5 .module('ds.objectDiff', [])
6 .factory('ObjectDiff', objectDiff)
7 .filter('toJsonView', toJsonViewFilter)
8 .filter('toJsonDiffView', toJsonDiffViewFilter)
9 .filter('objToJsonView', objToJsonViewFilter);
11 objectDiff.$inject = ['$sce'];
12 toJsonViewFilter.$inject = ['ObjectDiff'];
13 toJsonDiffViewFilter.$inject = ['ObjectDiff'];
14 objToJsonViewFilter.$inject = ['ObjectDiff'];
16 /* service implementation */
17 function objectDiff($sce) {
22 setOpenChar: setOpenChar,
23 setCloseChar: setCloseChar,
25 diffOwnProperties: diffOwnProperties,
26 toJsonView: formatToJsonXMLString,
27 objToJsonView: formatObjToJsonXMLString,
28 toJsonDiffView: formatChangesToXMLString
39 function setOpenChar(char) {
46 function setCloseChar(char) {
51 * diff between object a and b
58 function diff(a, b, shallow, isOwn) {
68 if ((!isOwn && key in b) || (isOwn && b.hasOwnProperty(key))) {
69 if (a[key] === b[key]) {
70 diffValue[key] = equalObj(a[key]);
72 if (!shallow && isValidAttr(a[key], b[key])) {
73 var valueDiff = diff(a[key], b[key], isOwn);
74 if (valueDiff.changed == 'equal') {
75 diffValue[key] = equalObj(a[key]);
78 diffValue[key] = valueDiff;
83 changed: 'primitive change',
99 if ((!isOwn && !(key in a)) || (isOwn && !a.hasOwnProperty(key))) {
112 changed: 'object change',
120 * diff between object a and b own properties only
126 function diffOwnProperties(a, b, deep) {
127 return diff(a, b, deep, true);
131 * Convert to a readable xml/html Json structure
132 * @param {Object} changes
136 function formatToJsonXMLString(changes, shallow) {
139 var diff = changes.value;
140 if (changes.changed == 'equal') {
141 return inspect(diff, shallow);
144 for (var key in diff) {
145 properties.push(formatChange(key, diff[key], shallow));
148 return $sce.trustAsHtml('<span>' + openChar + '</span>\n<div class="diff-level">' + properties.join('<span>,</span>\n') + '\n</div><span>' + openChar + '</span>');
153 * Convert to a readable xml/html Json structure
158 function formatObjToJsonXMLString(obj, shallow) {
159 return $sce.trustAsHtml(inspect(obj, shallow));
163 * Convert to a readable xml/html Json structure
164 * @param {Object} changes
168 function formatChangesToXMLString(changes, shallow) {
171 if (changes.changed == 'equal') {
175 var diff = changes.value;
177 for (var key in diff) {
178 var changed = diff[key].changed;
179 if (changed !== 'equal')
180 properties.push(formatChange(key, diff[key], shallow, true));
183 return $sce.trustAsHtml('<span>' + openChar + '</span>\n<div class="diff-level">' + properties.join('<span>,</span>\n') + '\n</div><span>' + closeChar + '</span>');
189 * @returns {{changed: string, value: *}}
191 function equalObj(obj) {
201 * @returns {*|boolean}
203 function isValidAttr(a, b) {
204 var typeA = typeof a;
205 var typeB = typeof b;
206 return (a && b && (typeA == 'object' || typeA == 'function') && (typeB == 'object' || typeB == 'function'));
216 function formatChange(key, diffItem, shallow, diffOnly) {
217 var changed = diffItem.changed;
221 property = (stringifyObjectKey(escapeHTML(key)) + '<span>: </span>' + inspect(diffItem.value));
225 property = ('<del class="diff">' + stringifyObjectKey(escapeHTML(key)) + '<span>: </span>' + inspect(diffItem.value) + '</del>');
229 property = ('<ins class="diff">' + stringifyObjectKey(escapeHTML(key)) + '<span>: </span>' + inspect(diffItem.value) + '</ins>');
232 case 'primitive change':
233 var prefix = stringifyObjectKey(escapeHTML(key)) + '<span>: </span>';
235 '<del class="diff diff-key">' + prefix + inspect(diffItem.removed) + '</del><span>,</span>\n' +
236 '<ins class="diff diff-key">' + prefix + inspect(diffItem.added) + '</ins>');
239 case 'object change':
240 property = shallow ? '' : (stringifyObjectKey(key) + '<span>: </span>' + ( diffOnly ? formatChangesToXMLString(diffItem) : formatToJsonXMLString(diffItem)));
248 * @param {string} key
251 function stringifyObjectKey(key) {
252 return /^[a-z0-9_$]*$/i.test(key) ?
258 * @param {string} string
261 function escapeHTML(string) {
262 return string.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
266 * @param {Object} obj
270 function inspect(obj, shallow) {
272 return _inspect('', obj, shallow);
275 * @param {string} accumulator
276 * @param {object} obj
277 * @see http://jsperf.com/continuation-passing-style/3
281 function _inspect(accumulator, obj, shallow) {
282 switch (typeof obj) {
285 accumulator += 'null';
289 accumulator += '[object]';
292 var keys = Object.keys(obj);
293 var length = keys.length;
295 accumulator += '<span>' + openChar + closeChar + '</span>';
297 accumulator += '<span>' + openChar + '</span>\n<div class="diff-level">';
298 for (var i = 0; i < length; i++) {
300 accumulator = _inspect(accumulator + stringifyObjectKey(escapeHTML(key)) + '<span>: </span>', obj[key]);
301 if (i < length - 1) {
302 accumulator += '<span>,</span>\n';
305 accumulator += '\n</div><span>' + closeChar + '</span>'
310 accumulator += JSON.stringify(escapeHTML(obj));
314 accumulator += 'undefined';
318 accumulator += escapeHTML(String(obj));
326 /* filter implementation */
327 function toJsonViewFilter(ObjectDiff) {
328 return function (value) {
329 return ObjectDiff.toJsonView(value);
333 function toJsonDiffViewFilter(ObjectDiff) {
334 return function (value) {
335 return ObjectDiff.toJsonDiffView(value);
339 function objToJsonViewFilter(ObjectDiff) {
340 return function (value) {
341 return ObjectDiff.objToJsonView(value);