diff2html
Advanced tools
Comparing version 0.2.4 to 0.2.5-1
966
lib/diff.js
@@ -16,551 +16,629 @@ /* See LICENSE file for terms of use */ | ||
* http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927 | ||
* | ||
* AUTHOR: https://github.com/kpdecker/jsdiff | ||
*/ | ||
(function(global, undefined) { | ||
(function (global, undefined) { | ||
var objectPrototypeToString = Object.prototype.toString; | ||
var JsDiff = (function() { | ||
/*jshint maxparams: 5*/ | ||
/*istanbul ignore next*/ | ||
function map(arr, mapper, that) { | ||
if (Array.prototype.map) { | ||
return Array.prototype.map.call(arr, mapper, that); | ||
} | ||
/*istanbul ignore next*/ | ||
function map(arr, mapper, that) { | ||
if (Array.prototype.map) { | ||
return Array.prototype.map.call(arr, mapper, that); | ||
} | ||
var other = new Array(arr.length); | ||
var other = new Array(arr.length); | ||
for (var i = 0, n = arr.length; i < n; i++) { | ||
other[i] = mapper.call(that, arr[i], i, arr); | ||
} | ||
return other; | ||
for (var i = 0, n = arr.length; i < n; i++) { | ||
other[i] = mapper.call(that, arr[i], i, arr); | ||
} | ||
function clonePath(path) { | ||
return { newPos: path.newPos, components: path.components.slice(0) }; | ||
} | ||
function removeEmpty(array) { | ||
var ret = []; | ||
for (var i = 0; i < array.length; i++) { | ||
if (array[i]) { | ||
ret.push(array[i]); | ||
} | ||
return other; | ||
} | ||
function clonePath(path) { | ||
return {newPos: path.newPos, components: path.components.slice(0)}; | ||
} | ||
function removeEmpty(array) { | ||
var ret = []; | ||
for (var i = 0; i < array.length; i++) { | ||
if (array[i]) { | ||
ret.push(array[i]); | ||
} | ||
return ret; | ||
} | ||
function escapeHTML(s) { | ||
var n = s; | ||
n = n.replace(/&/g, '&'); | ||
n = n.replace(/</g, '<'); | ||
n = n.replace(/>/g, '>'); | ||
n = n.replace(/"/g, '"'); | ||
return ret; | ||
} | ||
return n; | ||
} | ||
function escapeHTML(s) { | ||
var n = s; | ||
n = n.replace(/&/g, '&'); | ||
n = n.replace(/</g, '<'); | ||
n = n.replace(/>/g, '>'); | ||
n = n.replace(/"/g, '"'); | ||
function buildValues(components, newString, oldString, useLongestToken) { | ||
var componentPos = 0, | ||
componentLen = components.length, | ||
newPos = 0, | ||
oldPos = 0; | ||
return n; | ||
} | ||
for (; componentPos < componentLen; componentPos++) { | ||
var component = components[componentPos]; | ||
if (!component.removed) { | ||
if (!component.added && useLongestToken) { | ||
var value = newString.slice(newPos, newPos + component.count); | ||
value = map(value, function(value, i) { | ||
var oldValue = oldString[oldPos + i]; | ||
return oldValue.length > value.length ? oldValue : value; | ||
}); | ||
// This function handles the presence of circular references by bailing out when encountering an | ||
// object that is already on the "stack" of items being processed. | ||
function canonicalize(obj, stack, replacementStack) { | ||
stack = stack || []; | ||
replacementStack = replacementStack || []; | ||
component.value = value.join(''); | ||
} else { | ||
component.value = newString.slice(newPos, newPos + component.count).join(''); | ||
} | ||
newPos += component.count; | ||
var i; | ||
// Common case | ||
if (!component.added) { | ||
oldPos += component.count; | ||
} | ||
} else { | ||
component.value = oldString.slice(oldPos, oldPos + component.count).join(''); | ||
oldPos += component.count; | ||
} | ||
for (i = 0; i < stack.length; i += 1) { | ||
if (stack[i] === obj) { | ||
return replacementStack[i]; | ||
} | ||
} | ||
return components; | ||
var canonicalizedObj; | ||
if ('[object Array]' === objectPrototypeToString.call(obj)) { | ||
stack.push(obj); | ||
canonicalizedObj = new Array(obj.length); | ||
replacementStack.push(canonicalizedObj); | ||
for (i = 0; i < obj.length; i += 1) { | ||
canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack); | ||
} | ||
stack.pop(); | ||
replacementStack.pop(); | ||
} else if (typeof obj === 'object' && obj !== null) { | ||
stack.push(obj); | ||
canonicalizedObj = {}; | ||
replacementStack.push(canonicalizedObj); | ||
var sortedKeys = [], | ||
key; | ||
for (key in obj) { | ||
sortedKeys.push(key); | ||
} | ||
sortedKeys.sort(); | ||
for (i = 0; i < sortedKeys.length; i += 1) { | ||
key = sortedKeys[i]; | ||
canonicalizedObj[key] = canonicalize(obj[key], stack, replacementStack); | ||
} | ||
stack.pop(); | ||
replacementStack.pop(); | ||
} else { | ||
canonicalizedObj = obj; | ||
} | ||
return canonicalizedObj; | ||
} | ||
var Diff = function(ignoreWhitespace) { | ||
this.ignoreWhitespace = ignoreWhitespace; | ||
}; | ||
Diff.prototype = { | ||
diff: function(oldString, newString, callback) { | ||
var self = this; | ||
function buildValues(components, newString, oldString, useLongestToken) { | ||
var componentPos = 0, | ||
componentLen = components.length, | ||
newPos = 0, | ||
oldPos = 0; | ||
function done(value) { | ||
if (callback) { | ||
setTimeout(function() { callback(undefined, value); }, 0); | ||
return true; | ||
} else { | ||
return value; | ||
} | ||
for (; componentPos < componentLen; componentPos++) { | ||
var component = components[componentPos]; | ||
if (!component.removed) { | ||
if (!component.added && useLongestToken) { | ||
var value = newString.slice(newPos, newPos + component.count); | ||
value = map(value, function (value, i) { | ||
var oldValue = oldString[oldPos + i]; | ||
return oldValue.length > value.length ? oldValue : value; | ||
}); | ||
component.value = value.join(''); | ||
} else { | ||
component.value = newString.slice(newPos, newPos + component.count).join(''); | ||
} | ||
newPos += component.count; | ||
// Handle the identity case (this is due to unrolling editLength == 0 | ||
if (newString === oldString) { | ||
return done([{ value: newString }]); | ||
// Common case | ||
if (!component.added) { | ||
oldPos += component.count; | ||
} | ||
if (!newString) { | ||
return done([{ value: oldString, removed: true }]); | ||
} else { | ||
component.value = oldString.slice(oldPos, oldPos + component.count).join(''); | ||
oldPos += component.count; | ||
// Reverse add and remove so removes are output first to match common convention | ||
// The diffing algorithm is tied to add then remove output and this is the simplest | ||
// route to get the desired output with minimal overhead. | ||
if (componentPos && components[componentPos - 1].added) { | ||
var tmp = components[componentPos - 1]; | ||
components[componentPos - 1] = components[componentPos]; | ||
components[componentPos] = tmp; | ||
} | ||
if (!oldString) { | ||
return done([{ value: newString, added: true }]); | ||
} | ||
} | ||
} | ||
newString = this.tokenize(newString); | ||
oldString = this.tokenize(oldString); | ||
return components; | ||
} | ||
var newLen = newString.length, oldLen = oldString.length; | ||
var maxEditLength = newLen + oldLen; | ||
var bestPath = [{ newPos: -1, components: [] }]; | ||
function Diff(ignoreWhitespace) { | ||
this.ignoreWhitespace = ignoreWhitespace; | ||
} | ||
// Seed editLength = 0, i.e. the content starts with the same values | ||
var oldPos = this.extractCommon(bestPath[0], newString, oldString, 0); | ||
if (bestPath[0].newPos+1 >= newLen && oldPos+1 >= oldLen) { | ||
// Identity per the equality and tokenizer | ||
return done([{value: newString.join('')}]); | ||
Diff.prototype = { | ||
diff: function (oldString, newString, callback) { | ||
var self = this; | ||
function done(value) { | ||
if (callback) { | ||
setTimeout(function () { | ||
callback(undefined, value); | ||
}, 0); | ||
return true; | ||
} else { | ||
return value; | ||
} | ||
} | ||
// Main worker method. checks all permutations of a given edit length for acceptance. | ||
function execEditLength() { | ||
for (var diagonalPath = -1*editLength; diagonalPath <= editLength; diagonalPath+=2) { | ||
var basePath; | ||
var addPath = bestPath[diagonalPath-1], | ||
removePath = bestPath[diagonalPath+1]; | ||
oldPos = (removePath ? removePath.newPos : 0) - diagonalPath; | ||
if (addPath) { | ||
// No one else is going to attempt to use this value, clear it | ||
bestPath[diagonalPath-1] = undefined; | ||
} | ||
// Handle the identity case (this is due to unrolling editLength == 0 | ||
if (newString === oldString) { | ||
return done([{value: newString}]); | ||
} | ||
if (!newString) { | ||
return done([{value: oldString, removed: true}]); | ||
} | ||
if (!oldString) { | ||
return done([{value: newString, added: true}]); | ||
} | ||
var canAdd = addPath && addPath.newPos+1 < newLen; | ||
var canRemove = removePath && 0 <= oldPos && oldPos < oldLen; | ||
if (!canAdd && !canRemove) { | ||
// If this path is a terminal then prune | ||
bestPath[diagonalPath] = undefined; | ||
continue; | ||
} | ||
newString = this.tokenize(newString); | ||
oldString = this.tokenize(oldString); | ||
// Select the diagonal that we want to branch from. We select the prior | ||
// path whose position in the new string is the farthest from the origin | ||
// and does not pass the bounds of the diff graph | ||
if (!canAdd || (canRemove && addPath.newPos < removePath.newPos)) { | ||
basePath = clonePath(removePath); | ||
self.pushComponent(basePath.components, undefined, true); | ||
} else { | ||
basePath = addPath; // No need to clone, we've pulled it from the list | ||
basePath.newPos++; | ||
self.pushComponent(basePath.components, true, undefined); | ||
} | ||
var newLen = newString.length, oldLen = oldString.length; | ||
var editLength = 1; | ||
var maxEditLength = newLen + oldLen; | ||
var bestPath = [{newPos: -1, components: []}]; | ||
var oldPos = self.extractCommon(basePath, newString, oldString, diagonalPath); | ||
// Seed editLength = 0, i.e. the content starts with the same values | ||
var oldPos = this.extractCommon(bestPath[0], newString, oldString, 0); | ||
if (bestPath[0].newPos + 1 >= newLen && oldPos + 1 >= oldLen) { | ||
// Identity per the equality and tokenizer | ||
return done([{value: newString.join('')}]); | ||
} | ||
// If we have hit the end of both strings, then we are done | ||
if (basePath.newPos+1 >= newLen && oldPos+1 >= oldLen) { | ||
return done(buildValues(basePath.components, newString, oldString, self.useLongestToken)); | ||
} else { | ||
// Otherwise track this path as a potential candidate and continue. | ||
bestPath[diagonalPath] = basePath; | ||
} | ||
// Main worker method. checks all permutations of a given edit length for acceptance. | ||
function execEditLength() { | ||
for (var diagonalPath = -1 * editLength; diagonalPath <= editLength; diagonalPath += 2) { | ||
var basePath; | ||
var addPath = bestPath[diagonalPath - 1], | ||
removePath = bestPath[diagonalPath + 1], | ||
oldPos = (removePath ? removePath.newPos : 0) - diagonalPath; | ||
if (addPath) { | ||
// No one else is going to attempt to use this value, clear it | ||
bestPath[diagonalPath - 1] = undefined; | ||
} | ||
editLength++; | ||
} | ||
var canAdd = addPath && addPath.newPos + 1 < newLen, | ||
canRemove = removePath && 0 <= oldPos && oldPos < oldLen; | ||
if (!canAdd && !canRemove) { | ||
// If this path is a terminal then prune | ||
bestPath[diagonalPath] = undefined; | ||
continue; | ||
} | ||
// Performs the length of edit iteration. Is a bit fugly as this has to support the | ||
// sync and async mode which is never fun. Loops over execEditLength until a value | ||
// is produced. | ||
var editLength = 1; | ||
if (callback) { | ||
(function exec() { | ||
setTimeout(function() { | ||
// This should not happen, but we want to be safe. | ||
/*istanbul ignore next */ | ||
if (editLength > maxEditLength) { | ||
return callback(); | ||
} | ||
// Select the diagonal that we want to branch from. We select the prior | ||
// path whose position in the new string is the farthest from the origin | ||
// and does not pass the bounds of the diff graph | ||
if (!canAdd || (canRemove && addPath.newPos < removePath.newPos)) { | ||
basePath = clonePath(removePath); | ||
self.pushComponent(basePath.components, undefined, true); | ||
} else { | ||
basePath = addPath; // No need to clone, we've pulled it from the list | ||
basePath.newPos++; | ||
self.pushComponent(basePath.components, true, undefined); | ||
} | ||
if (!execEditLength()) { | ||
exec(); | ||
} | ||
}, 0); | ||
})(); | ||
} else { | ||
while(editLength <= maxEditLength) { | ||
var ret = execEditLength(); | ||
if (ret) { | ||
return ret; | ||
} | ||
oldPos = self.extractCommon(basePath, newString, oldString, diagonalPath); | ||
// If we have hit the end of both strings, then we are done | ||
if (basePath.newPos + 1 >= newLen && oldPos + 1 >= oldLen) { | ||
return done(buildValues(basePath.components, newString, oldString, self.useLongestToken)); | ||
} else { | ||
// Otherwise track this path as a potential candidate and continue. | ||
bestPath[diagonalPath] = basePath; | ||
} | ||
} | ||
}, | ||
pushComponent: function(components, added, removed) { | ||
var last = components[components.length-1]; | ||
if (last && last.added === added && last.removed === removed) { | ||
// We need to clone here as the component clone operation is just | ||
// as shallow array clone | ||
components[components.length-1] = {count: last.count + 1, added: added, removed: removed }; | ||
} else { | ||
components.push({count: 1, added: added, removed: removed }); | ||
} | ||
}, | ||
extractCommon: function(basePath, newString, oldString, diagonalPath) { | ||
var newLen = newString.length, | ||
oldLen = oldString.length, | ||
newPos = basePath.newPos, | ||
oldPos = newPos - diagonalPath, | ||
editLength++; | ||
} | ||
commonCount = 0; | ||
while (newPos+1 < newLen && oldPos+1 < oldLen && this.equals(newString[newPos+1], oldString[oldPos+1])) { | ||
newPos++; | ||
oldPos++; | ||
commonCount++; | ||
} | ||
// Performs the length of edit iteration. Is a bit fugly as this has to support the | ||
// sync and async mode which is never fun. Loops over execEditLength until a value | ||
// is produced. | ||
if (callback) { | ||
(function exec() { | ||
setTimeout(function () { | ||
// This should not happen, but we want to be safe. | ||
/*istanbul ignore next */ | ||
if (editLength > maxEditLength) { | ||
return callback(); | ||
} | ||
if (commonCount) { | ||
basePath.components.push({count: commonCount}); | ||
if (!execEditLength()) { | ||
exec(); | ||
} | ||
}, 0); | ||
}()); | ||
} else { | ||
while (editLength <= maxEditLength) { | ||
var ret = execEditLength(); | ||
if (ret) { | ||
return ret; | ||
} | ||
} | ||
} | ||
}, | ||
basePath.newPos = newPos; | ||
return oldPos; | ||
}, | ||
pushComponent: function (components, added, removed) { | ||
var last = components[components.length - 1]; | ||
if (last && last.added === added && last.removed === removed) { | ||
// We need to clone here as the component clone operation is just | ||
// as shallow array clone | ||
components[components.length - 1] = {count: last.count + 1, added: added, removed: removed}; | ||
} else { | ||
components.push({count: 1, added: added, removed: removed}); | ||
} | ||
}, | ||
extractCommon: function (basePath, newString, oldString, diagonalPath) { | ||
var newLen = newString.length, | ||
oldLen = oldString.length, | ||
newPos = basePath.newPos, | ||
oldPos = newPos - diagonalPath, | ||
equals: function(left, right) { | ||
var reWhitespace = /\S/; | ||
return left === right || (this.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right)); | ||
}, | ||
tokenize: function(value) { | ||
return value.split(''); | ||
commonCount = 0; | ||
while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(newString[newPos + 1], oldString[oldPos + 1])) { | ||
newPos++; | ||
oldPos++; | ||
commonCount++; | ||
} | ||
}; | ||
var CharDiff = new Diff(); | ||
if (commonCount) { | ||
basePath.components.push({count: commonCount}); | ||
} | ||
var WordDiff = new Diff(true); | ||
var WordWithSpaceDiff = new Diff(); | ||
WordDiff.tokenize = WordWithSpaceDiff.tokenize = function(value) { | ||
return removeEmpty(value.split(/(\s+|\b)/)); | ||
}; | ||
basePath.newPos = newPos; | ||
return oldPos; | ||
}, | ||
var CssDiff = new Diff(true); | ||
CssDiff.tokenize = function(value) { | ||
return removeEmpty(value.split(/([{}:;,]|\s+)/)); | ||
}; | ||
equals: function (left, right) { | ||
var reWhitespace = /\S/; | ||
return left === right || (this.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right)); | ||
}, | ||
tokenize: function (value) { | ||
return value.split(''); | ||
} | ||
}; | ||
var LineDiff = new Diff(); | ||
var CharDiff = new Diff(); | ||
var TrimmedLineDiff = new Diff(); | ||
TrimmedLineDiff.ignoreTrim = true; | ||
var WordDiff = new Diff(true); | ||
var WordWithSpaceDiff = new Diff(); | ||
WordDiff.tokenize = WordWithSpaceDiff.tokenize = function (value) { | ||
return removeEmpty(value.split(/(\s+|\b)/)); | ||
}; | ||
LineDiff.tokenize = TrimmedLineDiff.tokenize = function(value) { | ||
var retLines = [], | ||
lines = value.split(/^/m); | ||
for(var i = 0; i < lines.length; i++) { | ||
var line = lines[i], | ||
lastLine = lines[i - 1], | ||
lastLineLastChar = lastLine ? lastLine[lastLine.length - 1] : ''; | ||
var CssDiff = new Diff(true); | ||
CssDiff.tokenize = function (value) { | ||
return removeEmpty(value.split(/([{}:;,]|\s+)/)); | ||
}; | ||
// Merge lines that may contain windows new lines | ||
if (line === '\n' && lastLineLastChar === '\r') { | ||
retLines[retLines.length - 1] = retLines[retLines.length - 1].slice(0,-1) + '\r\n'; | ||
} else if (line) { | ||
if (this.ignoreTrim) { | ||
line = line.trim(); | ||
//add a newline unless this is the last line. | ||
if (i < lines.length - 1) { | ||
line += '\n'; | ||
} | ||
var LineDiff = new Diff(); | ||
var TrimmedLineDiff = new Diff(); | ||
TrimmedLineDiff.ignoreTrim = true; | ||
LineDiff.tokenize = TrimmedLineDiff.tokenize = function (value) { | ||
var retLines = [], | ||
lines = value.split(/^/m); | ||
for (var i = 0; i < lines.length; i++) { | ||
var line = lines[i], | ||
lastLine = lines[i - 1], | ||
lastLineLastChar = lastLine && lastLine[lastLine.length - 1]; | ||
// Merge lines that may contain windows new lines | ||
if (line === '\n' && lastLineLastChar === '\r') { | ||
retLines[retLines.length - 1] = retLines[retLines.length - 1].slice(0, -1) + '\r\n'; | ||
} else { | ||
if (this.ignoreTrim) { | ||
line = line.trim(); | ||
// add a newline unless this is the last line. | ||
if (i < lines.length - 1) { | ||
line += '\n'; | ||
} | ||
retLines.push(line); | ||
} | ||
retLines.push(line); | ||
} | ||
} | ||
return retLines; | ||
}; | ||
return retLines; | ||
}; | ||
var PatchDiff = new Diff(); | ||
PatchDiff.tokenize = function (value) { | ||
var ret = [], | ||
linesAndNewlines = value.split(/(\n|\r\n)/); | ||
var SentenceDiff = new Diff(); | ||
SentenceDiff.tokenize = function (value) { | ||
return removeEmpty(value.split(/(\S.+?[.!?])(?=\s+|$)/)); | ||
}; | ||
// Ignore the final empty token that occurs if the string ends with a new line | ||
if (!linesAndNewlines[linesAndNewlines.length - 1]) { | ||
linesAndNewlines.pop(); | ||
} | ||
var JsonDiff = new Diff(); | ||
// Discriminate between two lines of pretty-printed, serialized JSON where one of them has a | ||
// dangling comma and the other doesn't. Turns out including the dangling comma yields the nicest output: | ||
JsonDiff.useLongestToken = true; | ||
JsonDiff.tokenize = LineDiff.tokenize; | ||
JsonDiff.equals = function(left, right) { | ||
return LineDiff.equals(left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1')); | ||
}; | ||
// Merge the content and line separators into single tokens | ||
for (var i = 0; i < linesAndNewlines.length; i++) { | ||
var line = linesAndNewlines[i]; | ||
var objectPrototypeToString = Object.prototype.toString; | ||
if (i % 2) { | ||
ret[ret.length - 1] += line; | ||
} else { | ||
ret.push(line); | ||
} | ||
} | ||
return ret; | ||
}; | ||
// This function handles the presence of circular references by bailing out when encountering an | ||
// object that is already on the "stack" of items being processed. | ||
function canonicalize(obj, stack, replacementStack) { | ||
stack = stack || []; | ||
replacementStack = replacementStack || []; | ||
var SentenceDiff = new Diff(); | ||
SentenceDiff.tokenize = function (value) { | ||
return removeEmpty(value.split(/(\S.+?[.!?])(?=\s+|$)/)); | ||
}; | ||
var i; | ||
var JsonDiff = new Diff(); | ||
// Discriminate between two lines of pretty-printed, serialized JSON where one of them has a | ||
// dangling comma and the other doesn't. Turns out including the dangling comma yields the nicest output: | ||
JsonDiff.useLongestToken = true; | ||
JsonDiff.tokenize = LineDiff.tokenize; | ||
JsonDiff.equals = function (left, right) { | ||
return LineDiff.equals(left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1')); | ||
}; | ||
for (var i = 0 ; i < stack.length ; i += 1) { | ||
if (stack[i] === obj) { | ||
return replacementStack[i]; | ||
} | ||
} | ||
var JsDiff = { | ||
Diff: Diff, | ||
var canonicalizedObj; | ||
diffChars: function (oldStr, newStr, callback) { | ||
return CharDiff.diff(oldStr, newStr, callback); | ||
}, | ||
diffWords: function (oldStr, newStr, callback) { | ||
return WordDiff.diff(oldStr, newStr, callback); | ||
}, | ||
diffWordsWithSpace: function (oldStr, newStr, callback) { | ||
return WordWithSpaceDiff.diff(oldStr, newStr, callback); | ||
}, | ||
diffLines: function (oldStr, newStr, callback) { | ||
return LineDiff.diff(oldStr, newStr, callback); | ||
}, | ||
diffTrimmedLines: function (oldStr, newStr, callback) { | ||
return TrimmedLineDiff.diff(oldStr, newStr, callback); | ||
}, | ||
if ('[object Array]' === objectPrototypeToString.call(obj)) { | ||
stack.push(obj); | ||
canonicalizedObj = new Array(obj.length); | ||
replacementStack.push(canonicalizedObj); | ||
for (i = 0 ; i < obj.length ; i += 1) { | ||
canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack); | ||
} | ||
stack.pop(); | ||
replacementStack.pop(); | ||
} else if (typeof obj === 'object' && obj !== null) { | ||
stack.push(obj); | ||
canonicalizedObj = {}; | ||
replacementStack.push(canonicalizedObj); | ||
var sortedKeys = []; | ||
for (var key in obj) { | ||
sortedKeys.push(key); | ||
} | ||
sortedKeys.sort(); | ||
for (i = 0 ; i < sortedKeys.length ; i += 1) { | ||
var key = sortedKeys[i]; | ||
canonicalizedObj[key] = canonicalize(obj[key], stack, replacementStack); | ||
} | ||
stack.pop(); | ||
replacementStack.pop(); | ||
} else { | ||
canonicalizedObj = obj; | ||
} | ||
return canonicalizedObj; | ||
} | ||
diffSentences: function (oldStr, newStr, callback) { | ||
return SentenceDiff.diff(oldStr, newStr, callback); | ||
}, | ||
return { | ||
Diff: Diff, | ||
diffCss: function (oldStr, newStr, callback) { | ||
return CssDiff.diff(oldStr, newStr, callback); | ||
}, | ||
diffJson: function (oldObj, newObj, callback) { | ||
return JsonDiff.diff( | ||
typeof oldObj === 'string' ? oldObj : JSON.stringify(canonicalize(oldObj), undefined, ' '), | ||
typeof newObj === 'string' ? newObj : JSON.stringify(canonicalize(newObj), undefined, ' '), | ||
callback | ||
); | ||
}, | ||
diffChars: function(oldStr, newStr, callback) { return CharDiff.diff(oldStr, newStr, callback); }, | ||
diffWords: function(oldStr, newStr, callback) { return WordDiff.diff(oldStr, newStr, callback); }, | ||
diffWordsWithSpace: function(oldStr, newStr, callback) { return WordWithSpaceDiff.diff(oldStr, newStr, callback); }, | ||
diffLines: function(oldStr, newStr, callback) { return LineDiff.diff(oldStr, newStr, callback); }, | ||
diffTrimmedLines: function(oldStr, newStr, callback) { return TrimmedLineDiff.diff(oldStr, newStr, callback); }, | ||
createTwoFilesPatch: function (oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader) { | ||
var ret = []; | ||
diffSentences: function(oldStr, newStr, callback) { return SentenceDiff.diff(oldStr, newStr, callback); }, | ||
if (oldFileName == newFileName) { | ||
ret.push('Index: ' + oldFileName); | ||
} | ||
ret.push('==================================================================='); | ||
ret.push('--- ' + oldFileName + (typeof oldHeader === 'undefined' ? '' : '\t' + oldHeader)); | ||
ret.push('+++ ' + newFileName + (typeof newHeader === 'undefined' ? '' : '\t' + newHeader)); | ||
diffCss: function(oldStr, newStr, callback) { return CssDiff.diff(oldStr, newStr, callback); }, | ||
diffJson: function(oldObj, newObj, callback) { | ||
return JsonDiff.diff( | ||
typeof oldObj === 'string' ? oldObj : JSON.stringify(canonicalize(oldObj), undefined, ' '), | ||
typeof newObj === 'string' ? newObj : JSON.stringify(canonicalize(newObj), undefined, ' '), | ||
callback | ||
); | ||
}, | ||
var diff = PatchDiff.diff(oldStr, newStr); | ||
diff.push({value: '', lines: []}); // Append an empty value to make cleanup easier | ||
createPatch: function(fileName, oldStr, newStr, oldHeader, newHeader) { | ||
var ret = []; | ||
// Formats a given set of lines for printing as context lines in a patch | ||
function contextLines(lines) { | ||
return map(lines, function (entry) { | ||
return ' ' + entry; | ||
}); | ||
} | ||
ret.push('Index: ' + fileName); | ||
ret.push('==================================================================='); | ||
ret.push('--- ' + fileName + (typeof oldHeader === 'undefined' ? '' : '\t' + oldHeader)); | ||
ret.push('+++ ' + fileName + (typeof newHeader === 'undefined' ? '' : '\t' + newHeader)); | ||
// Outputs the no newline at end of file warning if needed | ||
function eofNL(curRange, i, current) { | ||
var last = diff[diff.length - 2], | ||
isLast = i === diff.length - 2, | ||
isLastOfType = i === diff.length - 3 && current.added !== last.added; | ||
var diff = LineDiff.diff(oldStr, newStr); | ||
if (!diff[diff.length-1].value) { | ||
diff.pop(); // Remove trailing newline add | ||
// Figure out if this is the last line for the given file and missing NL | ||
if (!(/\n$/.test(current.value)) && (isLast || isLastOfType)) { | ||
curRange.push('\\ No newline at end of file'); | ||
} | ||
diff.push({value: '', lines: []}); // Append an empty value to make cleanup easier | ||
} | ||
function contextLines(lines) { | ||
return map(lines, function(entry) { return ' ' + entry; }); | ||
} | ||
function eofNL(curRange, i, current) { | ||
var last = diff[diff.length-2], | ||
isLast = i === diff.length-2, | ||
isLastOfType = i === diff.length-3 && (current.added !== last.added || current.removed !== last.removed); | ||
var oldRangeStart = 0, newRangeStart = 0, curRange = [], | ||
oldLine = 1, newLine = 1; | ||
for (var i = 0; i < diff.length; i++) { | ||
var current = diff[i], | ||
lines = current.lines || current.value.replace(/\n$/, '').split('\n'); | ||
current.lines = lines; | ||
// Figure out if this is the last line for the given file and missing NL | ||
if (!/\n$/.test(current.value) && (isLast || isLastOfType)) { | ||
curRange.push('\\ No newline at end of file'); | ||
if (current.added || current.removed) { | ||
// If we have previous context, start with that | ||
if (!oldRangeStart) { | ||
var prev = diff[i - 1]; | ||
oldRangeStart = oldLine; | ||
newRangeStart = newLine; | ||
if (prev) { | ||
curRange = contextLines(prev.lines.slice(-4)); | ||
oldRangeStart -= curRange.length; | ||
newRangeStart -= curRange.length; | ||
} | ||
} | ||
} | ||
var oldRangeStart = 0, newRangeStart = 0, curRange = [], | ||
oldLine = 1, newLine = 1; | ||
for (var i = 0; i < diff.length; i++) { | ||
var current = diff[i], | ||
lines = current.lines || current.value.replace(/\n$/, '').split('\n'); | ||
current.lines = lines; | ||
// Output our changes | ||
curRange.push.apply(curRange, map(lines, function (entry) { | ||
return (current.added ? '+' : '-') + entry; | ||
})); | ||
eofNL(curRange, i, current); | ||
if (current.added || current.removed) { | ||
if (!oldRangeStart) { | ||
var prev = diff[i-1]; | ||
oldRangeStart = oldLine; | ||
newRangeStart = newLine; | ||
if (prev) { | ||
curRange = contextLines(prev.lines.slice(-4)); | ||
oldRangeStart -= curRange.length; | ||
newRangeStart -= curRange.length; | ||
// Track the updated file position | ||
if (current.added) { | ||
newLine += lines.length; | ||
} else { | ||
oldLine += lines.length; | ||
} | ||
} else { | ||
// Identical context lines. Track line changes | ||
if (oldRangeStart) { | ||
// Close out any changes that have been output (or join overlapping) | ||
if (lines.length <= 8 && i < diff.length - 2) { | ||
// Overlapping | ||
curRange.push.apply(curRange, contextLines(lines)); | ||
} else { | ||
// end the range and output | ||
var contextSize = Math.min(lines.length, 4); | ||
ret.push( | ||
'@@ -' + oldRangeStart + ',' + (oldLine - oldRangeStart + contextSize) | ||
+ ' +' + newRangeStart + ',' + (newLine - newRangeStart + contextSize) | ||
+ ' @@'); | ||
ret.push.apply(ret, curRange); | ||
ret.push.apply(ret, contextLines(lines.slice(0, contextSize))); | ||
if (lines.length <= 4) { | ||
eofNL(ret, i, current); | ||
} | ||
} | ||
curRange.push.apply(curRange, map(lines, function(entry) { return (current.added?'+':'-') + entry; })); | ||
eofNL(curRange, i, current); | ||
if (current.added) { | ||
newLine += lines.length; | ||
} else { | ||
oldLine += lines.length; | ||
oldRangeStart = 0; | ||
newRangeStart = 0; | ||
curRange = []; | ||
} | ||
} else { | ||
if (oldRangeStart) { | ||
// Close out any changes that have been output (or join overlapping) | ||
if (lines.length <= 8 && i < diff.length-2) { | ||
// Overlapping | ||
curRange.push.apply(curRange, contextLines(lines)); | ||
} else { | ||
// end the range and output | ||
var contextSize = Math.min(lines.length, 4); | ||
ret.push( | ||
'@@ -' + oldRangeStart + ',' + (oldLine-oldRangeStart+contextSize) | ||
+ ' +' + newRangeStart + ',' + (newLine-newRangeStart+contextSize) | ||
+ ' @@'); | ||
ret.push.apply(ret, curRange); | ||
ret.push.apply(ret, contextLines(lines.slice(0, contextSize))); | ||
if (lines.length <= 4) { | ||
eofNL(ret, i, current); | ||
} | ||
oldRangeStart = 0; newRangeStart = 0; curRange = []; | ||
} | ||
} | ||
oldLine += lines.length; | ||
newLine += lines.length; | ||
} | ||
oldLine += lines.length; | ||
newLine += lines.length; | ||
} | ||
} | ||
return ret.join('\n') + '\n'; | ||
}, | ||
return ret.join('\n') + '\n'; | ||
}, | ||
applyPatch: function(oldStr, uniDiff) { | ||
var diffstr = uniDiff.split('\n'); | ||
var diff = []; | ||
var i = 0, | ||
remEOFNL = false, | ||
addEOFNL = false; | ||
createPatch: function (fileName, oldStr, newStr, oldHeader, newHeader) { | ||
return JsDiff.createTwoFilesPatch(fileName, fileName, oldStr, newStr, oldHeader, newHeader); | ||
}, | ||
// Skip to the first change chunk | ||
while (i < diffstr.length && !/^@@/.test(diffstr[i])) { | ||
i++; | ||
} | ||
applyPatch: function (oldStr, uniDiff) { | ||
var diffstr = uniDiff.split('\n'), | ||
hunks = [], | ||
i = 0, | ||
remEOFNL = false, | ||
addEOFNL = false; | ||
for (; i < diffstr.length; i++) { | ||
if (diffstr[i][0] === '@') { | ||
var meh = diffstr[i].split(/@@ -(\d+),(\d+) \+(\d+),(\d+) @@/); | ||
diff.unshift({ | ||
start:meh[3], | ||
oldlength:meh[2], | ||
oldlines:[], | ||
newlength:meh[4], | ||
newlines:[] | ||
}); | ||
} else if (diffstr[i][0] === '+') { | ||
diff[0].newlines.push(diffstr[i].substr(1)); | ||
} else if (diffstr[i][0] === '-') { | ||
diff[0].oldlines.push(diffstr[i].substr(1)); | ||
} else if (diffstr[i][0] === ' ') { | ||
diff[0].newlines.push(diffstr[i].substr(1)); | ||
diff[0].oldlines.push(diffstr[i].substr(1)); | ||
} else if (diffstr[i][0] === '\\') { | ||
if (diffstr[i-1][0] === '+') { | ||
remEOFNL = true; | ||
} else if (diffstr[i-1][0] === '-') { | ||
addEOFNL = true; | ||
} | ||
// Skip to the first change hunk | ||
while (i < diffstr.length && !(/^@@/.test(diffstr[i]))) { | ||
i++; | ||
} | ||
// Parse the unified diff | ||
for (; i < diffstr.length; i++) { | ||
if (diffstr[i][0] === '@') { | ||
var chnukHeader = diffstr[i].split(/@@ -(\d+),(\d+) \+(\d+),(\d+) @@/); | ||
hunks.unshift({ | ||
start: chnukHeader[3], | ||
oldlength: +chnukHeader[2], | ||
removed: [], | ||
newlength: chnukHeader[4], | ||
added: [] | ||
}); | ||
} else if (diffstr[i][0] === '+') { | ||
hunks[0].added.push(diffstr[i].substr(1)); | ||
} else if (diffstr[i][0] === '-') { | ||
hunks[0].removed.push(diffstr[i].substr(1)); | ||
} else if (diffstr[i][0] === ' ') { | ||
hunks[0].added.push(diffstr[i].substr(1)); | ||
hunks[0].removed.push(diffstr[i].substr(1)); | ||
} else if (diffstr[i][0] === '\\') { | ||
if (diffstr[i - 1][0] === '+') { | ||
remEOFNL = true; | ||
} else if (diffstr[i - 1][0] === '-') { | ||
addEOFNL = true; | ||
} | ||
} | ||
} | ||
var str = oldStr.split('\n'); | ||
for (var i = diff.length - 1; i >= 0; i--) { | ||
var d = diff[i]; | ||
for (var j = 0; j < d.oldlength; j++) { | ||
if (str[d.start-1+j] !== d.oldlines[j]) { | ||
return false; | ||
} | ||
// Apply the diff to the input | ||
var lines = oldStr.split('\n'); | ||
for (i = hunks.length - 1; i >= 0; i--) { | ||
var hunk = hunks[i]; | ||
// Sanity check the input string. Bail if we don't match. | ||
for (var j = 0; j < hunk.oldlength; j++) { | ||
if (lines[hunk.start - 1 + j] !== hunk.removed[j]) { | ||
return false; | ||
} | ||
Array.prototype.splice.apply(str,[d.start-1,+d.oldlength].concat(d.newlines)); | ||
} | ||
Array.prototype.splice.apply(lines, [hunk.start - 1, hunk.oldlength].concat(hunk.added)); | ||
} | ||
if (remEOFNL) { | ||
while (!str[str.length-1]) { | ||
str.pop(); | ||
} | ||
} else if (addEOFNL) { | ||
str.push(''); | ||
// Handle EOFNL insertion/removal | ||
if (remEOFNL) { | ||
while (!lines[lines.length - 1]) { | ||
lines.pop(); | ||
} | ||
return str.join('\n'); | ||
}, | ||
} else if (addEOFNL) { | ||
lines.push(''); | ||
} | ||
return lines.join('\n'); | ||
}, | ||
convertChangesToXML: function(changes){ | ||
var ret = []; | ||
for ( var i = 0; i < changes.length; i++) { | ||
var change = changes[i]; | ||
if (change.added) { | ||
ret.push('<ins>'); | ||
} else if (change.removed) { | ||
ret.push('<del>'); | ||
} | ||
convertChangesToXML: function (changes) { | ||
var ret = []; | ||
for (var i = 0; i < changes.length; i++) { | ||
var change = changes[i]; | ||
if (change.added) { | ||
ret.push('<ins>'); | ||
} else if (change.removed) { | ||
ret.push('<del>'); | ||
} | ||
ret.push(escapeHTML(change.value)); | ||
ret.push(escapeHTML(change.value)); | ||
if (change.added) { | ||
ret.push('</ins>'); | ||
} else if (change.removed) { | ||
ret.push('</del>'); | ||
} | ||
if (change.added) { | ||
ret.push('</ins>'); | ||
} else if (change.removed) { | ||
ret.push('</del>'); | ||
} | ||
return ret.join(''); | ||
}, | ||
} | ||
return ret.join(''); | ||
}, | ||
// See: http://code.google.com/p/google-diff-match-patch/wiki/API | ||
convertChangesToDMP: function(changes){ | ||
var ret = [], change; | ||
for ( var i = 0; i < changes.length; i++) { | ||
change = changes[i]; | ||
ret.push([(change.added ? 1 : change.removed ? -1 : 0), change.value]); | ||
// See: http://code.google.com/p/google-diff-match-patch/wiki/API | ||
convertChangesToDMP: function (changes) { | ||
var ret = [], | ||
change, | ||
operation; | ||
for (var i = 0; i < changes.length; i++) { | ||
change = changes[i]; | ||
if (change.added) { | ||
operation = 1; | ||
} else if (change.removed) { | ||
operation = -1; | ||
} else { | ||
operation = 0; | ||
} | ||
return ret; | ||
}, | ||
canonicalize: canonicalize | ||
}; | ||
})(); | ||
ret.push([operation, change.value]); | ||
} | ||
return ret; | ||
}, | ||
canonicalize: canonicalize | ||
}; | ||
/*istanbul ignore next */ | ||
/*global module */ | ||
if (typeof module !== 'undefined' && module.exports) { | ||
module.exports = JsDiff; | ||
} | ||
else if (typeof define === 'function' && define.amd) { | ||
} else if (typeof define === 'function' && define.amd) { | ||
/*global define */ | ||
define([], function() { return JsDiff; }); | ||
} | ||
else if (typeof global.JsDiff === 'undefined') { | ||
define([], function () { | ||
return JsDiff; | ||
}); | ||
} else if (typeof global.JsDiff === 'undefined') { | ||
global.JsDiff = JsDiff; | ||
} | ||
})(this); | ||
}(this)); |
{ | ||
"name": "diff2html", | ||
"version": "0.2.4", | ||
"homepage": "https://www.github.com/rtfpessoa/diff2html-nodejs", | ||
"version": "0.2.5-1", | ||
"homepage": "https://www.github.com/rtfpessoa/diff2html", | ||
"description": "Fast Diff to colorized HTML", | ||
@@ -32,7 +31,7 @@ "keywords": [ | ||
"type": "git", | ||
"url": "https://www.github.com/rtfpessoa/diff2html-nodejs.git" | ||
"url": "https://www.github.com/rtfpessoa/diff2html.git" | ||
}, | ||
"bugs": { | ||
"url": "https://www.github.com/rtfpessoa/diff2html-nodejs/issues" | ||
"url": "https://www.github.com/rtfpessoa/diff2html/issues" | ||
}, | ||
@@ -57,14 +56,8 @@ | ||
"dependencies": { | ||
"diff": "1.3.2" | ||
"diff": "1.4.0" | ||
}, | ||
"devDependencies": { | ||
}, | ||
"devDependencies": {}, | ||
"licenses": [ | ||
{ | ||
"type": "MIT", | ||
"url": "https://github.com/rtfpessoa/diff2html-node/blob/master/LICENSE" | ||
} | ||
], | ||
"license": "MIT", | ||
@@ -71,0 +64,0 @@ "files": [ |
@@ -1,9 +0,5 @@ | ||
# Diff to Html Node Module by [rtfpessoa](https://github.com/rtfpessoa) | ||
# Diff to Html by [rtfpessoa](https://github.com/rtfpessoa) | ||
Diff to Html generates pretty HTML diffs. | ||
Diff to Html generates pretty HTML diffs from git word diff output. | ||
### Inspiration | ||
This project uses [diff2html](https://github.com/rtfpessoa/diff2html) as base. | ||
## Features | ||
@@ -23,22 +19,14 @@ | ||
## Other Distributions | ||
## Distributions | ||
* [WebJar](http://www.webjars.org/) | ||
* Manually download and import [diff2html.js](https://github.com/rtfpessoa/diff2html) into your page | ||
* [Node Module](https://www.npmjs.org/package/diff2html) | ||
* Node Package CLI [diff2html-cli](https://github.com/rtfpessoa/diff2html-nodejs-cli) | ||
* [Bower Package](http://bower.io/search/?q=diff2html) | ||
## Setup | ||
* Manually download and import `dist/diff2html.min.js` into your page | ||
sudo npm install -g diff2html | ||
## How to use | ||
## Usage | ||
Require the library using: | ||
``` | ||
var Diff2Html = require('diff2html').Diff2Html; | ||
``` | ||
> Pretty Line-by-Line Html From Git Word Diff Output | ||
@@ -64,6 +52,8 @@ | ||
## Contribution | ||
> Check out the `index.html` for a complete example. | ||
All contributions are welcome. | ||
## Contributions | ||
All the contributions are welcome. | ||
To contribute just send a pull request with your feature,fix,... and it will be reviewed asap. | ||
@@ -75,2 +65,6 @@ | ||
## Thanks | ||
This project is inspired in [pretty-diff](https://github.com/scottgonzalez/pretty-diff) by [Scott González](https://github.com/scottgonzalez). | ||
--- |
@@ -11,3 +11,5 @@ /* | ||
// dirty hack for browser compatibility | ||
var jsDiff = (typeof JsDiff !== "undefined" && JsDiff) || require("../lib/diff.js"); | ||
var jsDiff = (typeof JsDiff !== "undefined" && JsDiff) || | ||
require("diff") || | ||
require("../lib/diff.js"); | ||
var utils = require("./utils.js").Utils; | ||
@@ -14,0 +16,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
50945
13
1227
0
2
68
+ Addeddiff@1.4.0(transitive)
- Removeddiff@1.3.2(transitive)
Updateddiff@1.4.0