JavaScript xdiff_string_diff
!No description available for xdiff_string_diff. @php.js developers: Please update the function summary text file.
1 2 3 4 56 7 8 9 1011 12 13 14 1516 17 18 19 2021 22 23 24 2526 27 28 29 3031 32 33 34 3536 37 38 39 4041 42 43 44 4546 47 48 49 5051 52 53 54 5556 57 58 59 6061 62 63 64 6566 67 68 69 7071 72 73 74 7576 77 78 79 8081 82 83 84 8586 87 88 89 9091 92 93 94 9596 97 98 99 100101 102 103 104 105106 107 108 109 110111 112 113 114 115116 117 118 119 120121 122 123 124 125126 127 128 129 130131 132 133 134 135136 137 138 139 140141 142 143 144 145146 147 148 149 150151 152 153 154 155156 157 158 159 160161 162 163 164 165166 167 168 169 170171 172 173 174 175176 177 178 179 180181 182 183 184 185186 187 188 189 190191 192 193 194 195196 197 198 199 200201 202 203 204 205206 207 208 209 210211 212 213 214 215216 217 218 219 220221 222 223 224 225226 227 228 229 230231 232 233 234 235236 237 238 239 240241 242 243 244 245246 247 248 249 250251 252 253 254 255256 257 258 259 260261 262 263 264 265266 267 268 269 270271 272 273 274 275276 277 278 279 280281 282 283 284 285286 287 288 289 290291 292 293 294 295296 297 298 299 300301 302 303 304 305306 307 308 309 310311 312 313 314 315316 317 318 319 320321 322 323 324 325326 327 328 329 330331 332 333 334 335336 337 338 339 340341 342 343 344 345346 347 348 349 350351 352 353 354 355356 357 358 359 360361 362 363 364 365366 367 368 369 370371 372 373 374 375376 377 378 379 380381 382 383 384 385386 387 388 389 390391 392 393 394 395396 397 398 399 400401 402 403 404 405406 407 408 409 410411 412 413 414 415416 417 418 419 | function xdiff_string_diff (old_data, new_data, context_lines, minimal) { // !No description available for xdiff_string_diff. @php.js developers: Please update the function summary text file. // // version: 1109.2015 // discuss at: http://phpjs.org/functions/xdiff_string_diff // + original by: Brett Zamir (http://brett-zamir.me) // + based on: Imgen Tata (http://www.myipdf.com/) // + bugfixed by: Imgen Tata (http://www.myipdf.com/) // % note 1: The minimal argument is not currently supported // * example 1: xdiff_string_diff('', 'Hello world!'); // * returns 1: '@@ -0,0 +1,1 @@\n+Hello world!' // (This code was done by Imgen Tata; I have only reformatted for use in php.js) // See http://en.wikipedia.org/wiki/Diff#Unified_format var i = 0, j = 0, k = 0, ori_hunk_start, new_hunk_start, ori_hunk_end, new_hunk_end, ori_hunk_line_no, new_hunk_line_no, ori_hunk_size, new_hunk_size, // Potential configuration MAX_CONTEXT_LINES = Number.POSITIVE_INFINITY, MIN_CONTEXT_LINES = 0, DEFAULT_CONTEXT_LINES = 3, // HEADER_PREFIX = '@@ ', HEADER_SUFFIX = ' @@', ORIGINAL_INDICATOR = '-', NEW_INDICATOR = '+', RANGE_SEPARATOR = ',', CONTEXT_INDICATOR = ' ', DELETION_INDICATOR = '-', ADDITION_INDICATOR = '+', ori_lines, new_lines, NEW_LINE = '\n', /* *Trims string */ trim = function (text) { if (typeof text != 'string') { throw Error('String parameter required'); } return text.replace(/(^\s*)|(\s*$)/g, ''); }, /* *Verifies type of arguments */ verify_type = function (type) { var args = arguments, args_len = arguments.length, basic_types = ['number', 'boolean', 'string', 'function', 'object', 'undefined'], basic_type, i, j, type_of_type = typeof type; if (type_of_type != 'string' && type_of_type != 'function') { throw new Error('Bad type parameter'); } if (args_len < 2) { throw new Error('Too few arguments'); } if (type_of_type == 'string') { type = trim(type); if (type == '') { throw new Error('Bad type parameter'); } for (j = 0; j < basic_types.length; j++) { basic_type = basic_types[j]; if (basic_type == type) { for (i = 1; i < args_len; i++) { if (typeof args[i] != type) { throw new Error('Bad type'); } } return; } } throw new Error('Bad type parameter'); } // Not basic type. we need to use instanceof operator for (i = 1; i < args_len; i++) { if (!(args[i] instanceof type)) { throw new Error('Bad type'); } } }, /* *Checks if the specified array contains an element with specified value */ has_value = function (array, value) { var i; verify_type(Array, array); for (i = 0; i < array.length; i++) { if (array[i] === value) { return true; } } return false; }, /* *Checks the type of arguments *@param {String | Function} type Specifies the desired type *@return {Boolean} Return true if all arguments after the type argument are of specified type. Else false */ are_type_of = function (type) { var args = arguments, args_len = arguments.length, basic_types = ['number', 'boolean', 'string', 'function', 'object', 'undefined'], basic_type, i, j, type_of_type = typeof type; if (type_of_type != 'string' && type_of_type != 'function') { throw new Error('Bad type parameter'); } if (args_len < 2) { throw new Error('Too few arguments'); } if (type_of_type == 'string') { type = trim(type); if (type == '') { return false; } for (j = 0; j < basic_types.length; j++) { basic_type = basic_types[j]; if (basic_type == type) { for (i = 1; i < args_len; i++) { if (typeof args[i] != type) { return false; } } return true; } } throw new Error('Bad type parameter'); } // Not basic type. we need to use instanceof operator for (i = 1; i < args_len; i++) { if (!(args[i] instanceof type)) { return false; } } return true; },/* *Initialize and return an array with specified size and initial value */ get_initialized_array = function (array_size, init_value) { var array = [], i; verify_type('number', array_size); for (i = 0; i < array_size; i++) { array.push(init_value); } return array; }, /* *Splits text into lines and return as a string array */ split_into_lines = function (text) { verify_type('string', text); if (text == '') { return []; } return text.split('\n'); }, is_empty_array = function (obj) { return are_type_of(Array, obj) && obj.length === 0; }, /* * Finds longest common sequence between two sequences *See http://wordaligned.org/articles/longest-common-subsequence */ find_longest_common_sequence = function (seq1, seq2, seq1_is_in_lcs, seq2_is_in_lcs) { if (!are_type_of(Array, seq1, seq2)) { throw new Error('Array parameters are required'); } // Deal with edge case if (is_empty_array(seq1) || is_empty_array(seq2)) { return []; } // Function to calculate lcs lengths var lcs_lens = function (xs, ys) { var curr = get_initialized_array(ys.length + 1, 0); var prev; var i, j; for (i = 0; i < xs.length; i++) { prev = curr.slice(0); for (j = 0; j < ys.length; j++) { if (xs[i] === ys[j]) { curr[j + 1] = prev[j] + 1; } else { curr[j + 1] = Math.max(curr[j], prev[j + 1]); } } } return curr; }, // Function to find lcs and fill in the array to indicate the optimal longest common sequence find_lcs = function (xs, xidx, xs_is_in, ys) { var nx = xs.length; var ny = ys.length; var i; var xb, xe; var ll_b, ll_e; var pivot; var max; var yb, ye; if (nx === 0) { return []; } else if (nx === 1) { if (has_value(ys, xs[0])) { xs_is_in[xidx] = true; return [xs[0]]; } else { return []; } } else { i = Math.floor(nx / 2); xb = xs.slice(0, i); xe = xs.slice(i); ll_b = lcs_lens(xb, ys); ll_e = lcs_lens(xe.slice(0).reverse(), ys.slice(0).reverse()); pivot = 0; max = 0; for (j = 0; j <= ny; j++) { if (ll_b[j] + ll_e[ny - j] > max) { pivot = j; max = ll_b[j] + ll_e[ny - j]; } } yb = ys.slice(0, pivot); ye = ys.slice(pivot); return find_lcs(xb, xidx, xs_is_in, yb).concat(find_lcs(xe, xidx + i, xs_is_in, ye)); } }; // Fill in seq1_is_in_lcs to find the optimal longest common subsequence of first sequence find_lcs(seq1, 0, seq1_is_in_lcs, seq2); // Fill in seq2_is_in_lcs to find the optimal longest common subsequence of second sequence and return the result return find_lcs(seq2, 0, seq2_is_in_lcs, seq1); }; // First, check the parameters if (are_type_of('string', old_data, new_data) === false) { return false; } if (old_data == new_data) { return ''; } if (typeof context_lines != 'number' || context_lines > MAX_CONTEXT_LINES || context_lines < MIN_CONTEXT_LINES) { context_lines = DEFAULT_CONTEXT_LINES; } ori_lines = split_into_lines(old_data); new_lines = split_into_lines(new_data); var ori_len = ori_lines.length, new_len = new_lines.length, ori_is_in_lcs = get_initialized_array(ori_len, false), new_is_in_lcs = get_initialized_array(new_len, false), lcs_len = find_longest_common_sequence(ori_lines, new_lines, ori_is_in_lcs, new_is_in_lcs).length, unidiff = ''; if (lcs_len === 0) { // No common sequence unidiff = HEADER_PREFIX + ORIGINAL_INDICATOR + (ori_len > 0 ? '1' : '0') + RANGE_SEPARATOR + ori_len + ' ' + NEW_INDICATOR + (new_len > 0 ? '1' : '0') + RANGE_SEPARATOR + new_len + HEADER_SUFFIX; for (i = 0; i < ori_len; i++) { unidiff += NEW_LINE + DELETION_INDICATOR + ori_lines[i]; } for (j = 0; j < new_len; j++) { unidiff += NEW_LINE + ADDITION_INDICATOR + new_lines[j]; } return unidiff; } var leading_context = [], trailing_context = [], actual_leading_context = [], actual_trailing_context = [], // Regularize leading context by the context_lines parameter regularize_leading_context = function (context) { if (context.length === 0 || context_lines === 0) { return []; } var context_start_pos = Math.max(context.length - context_lines, 0); return context.slice(context_start_pos); }, // Regularize trailing context by the context_lines parameter regularize_trailing_context = function (context) { if (context.length === 0 || context_lines === 0) { return []; } return context.slice(0, Math.min(context_lines, context.length)); }; // Skip common lines in the beginning while (i < ori_len && ori_is_in_lcs[i] === true && new_is_in_lcs[i] === true) { leading_context.push(ori_lines[i]); i++; } j = i; k = i; // The index in the longest common sequence ori_hunk_start = i; new_hunk_start = j; ori_hunk_end = i; new_hunk_end = j; while (i < ori_len || j < new_len) { while (i < ori_len && ori_is_in_lcs[i] === false) { i++; } ori_hunk_end = i; while (j < new_len && new_is_in_lcs[j] === false) { j++; } new_hunk_end = j; // Find the trailing context trailing_context = []; while (i < ori_len && ori_is_in_lcs[i] === true && j < new_len && new_is_in_lcs[j] === true) { trailing_context.push(ori_lines[i]); k++; i++; j++; } if (k >= lcs_len || // No more in longest common lines trailing_context.length >= 2 * context_lines) { // Context break found if (trailing_context.length < 2 * context_lines) { // It must be last block of common lines but not a context break trailing_context = []; // Force break out i = ori_len; j = new_len; // Update hunk ends to force output to the end ori_hunk_end = ori_len; new_hunk_end = new_len; } // Output the diff hunk // Trim the leading and trailing context block actual_leading_context = regularize_leading_context(leading_context); actual_trailing_context = regularize_trailing_context(trailing_context); ori_hunk_start -= actual_leading_context.length; new_hunk_start -= actual_leading_context.length; ori_hunk_end += actual_trailing_context.length; new_hunk_end += actual_trailing_context.length; ori_hunk_line_no = ori_hunk_start + 1; new_hunk_line_no = new_hunk_start + 1; ori_hunk_size = ori_hunk_end - ori_hunk_start; new_hunk_size = new_hunk_end - new_hunk_start; // Build header unidiff += HEADER_PREFIX + ORIGINAL_INDICATOR + ori_hunk_line_no + RANGE_SEPARATOR + ori_hunk_size + ' ' + NEW_INDICATOR + new_hunk_line_no + RANGE_SEPARATOR + new_hunk_size + HEADER_SUFFIX + NEW_LINE; // Build the diff hunk content while (ori_hunk_start < ori_hunk_end || new_hunk_start < new_hunk_end) { if (ori_hunk_start < ori_hunk_end && ori_is_in_lcs[ori_hunk_start] === true && new_is_in_lcs[new_hunk_start] === true) { // The context line unidiff += CONTEXT_INDICATOR + ori_lines[ori_hunk_start] + NEW_LINE; ori_hunk_start++; new_hunk_start++; } else if (ori_hunk_start < ori_hunk_end && ori_is_in_lcs[ori_hunk_start] === false) { // The deletion line unidiff += DELETION_INDICATOR + ori_lines[ori_hunk_start] + NEW_LINE; ori_hunk_start++; } else if (new_hunk_start < new_hunk_end && new_is_in_lcs[new_hunk_start] === false) { // The additional line unidiff += ADDITION_INDICATOR + new_lines[new_hunk_start] + NEW_LINE; new_hunk_start++; } } // Update hunk position and leading context ori_hunk_start = i; new_hunk_start = j; leading_context = trailing_context; } } // Trim the trailing new line if it exists if (unidiff.length > 0 && unidiff.charAt(unidiff.length) === NEW_LINE) { unidiff = unidiff.slice(0, -1); } return unidiff; } |
Examples
Running
1 | xdiff_string_diff('', 'Hello world!'); |
Should return
1 | '@@ -0,0 +1,1 @@\n+Hello world!' |
Dependencies
No dependencies, you can use this function standalone.
Open syntax issues
php.js uses JsLint to help us keep our code consistent and prevent some common bugs.
Eventually we want all code to pass or at least take into consideration most fixes suggested by JsLint, following this JsLint configuration we’ve decided on.
Authors
Thanks to the following developers, you get to have xdiff_string_diff goodness in JavaScript.
No comments yet. Be the first!
spread the word:
Use any PHP function in JavaScript
These kind folks have already donated: AYHAN BARI*, Nikita Ekshiyan, Nikita Ekshiyan, Petr Pavel, @HalfWinter, Paulo Freitas, Andros Peña Romo, @andorosu, Raimund Szabo, Nitin Gupta, @nikosdion, Anonymous, Anonymous and Shawn Houser.
<your name here>