Skip to content

Instantly share code, notes, and snippets.

@fnordfish
Created February 16, 2021 13:22
Show Gist options
  • Save fnordfish/1dd9bec0fa903ff6d8d2bde56a025408 to your computer and use it in GitHub Desktop.
Save fnordfish/1dd9bec0fa903ff6d8d2bde56a025408 to your computer and use it in GitHub Desktop.
DMP - diff-match-patch using it's JS distribution and mini_racer
# frozen_string_literal: true
# renamed files for gist compatibility - should be in sub-folders (dmp/*)
require_relative "dmp__diff"
require_relative "dmp__render"
# Diff-Match-Path
# https://github.com/google/diff-match-patch
module DMP
extend Diff
extend Renderer
end
# frozen_string_literal: true
require "mini_racer"
# Diff-Match-Path
# Converts the original "-1, 0, 1" diff identifiers to non-negative numbers
# https://github.com/google/diff-match-patch
module DMP
module Diff
DIFF_EQUAL = 0
DIFF_DELETE = 1 # converted from -1
DIFF_INSERT = 2 # converted from 1
CONVERSION = { 0 => DIFF_EQUAL, -1 => DIFF_DELETE, 1 => DIFF_INSERT }.freeze
NL_REGEXP = /(\r\n|\n|\r)/
LF = "\n"
# @private
SCRIPT = <<~JS
;function dmp(left, right, semantic_cleanup) {
var dmp = new diff_match_patch();
// Number of seconds to map a diff before giving up (0 for infinity).
dmp.Diff_Timeout = 2.0;
// Cost of an empty edit operation in terms of edit characters.
dmp.Diff_EditCost = 4;
var diffs = dmp.diff_main(left, right);
if (semantic_cleanup) {
dmp.diff_cleanupSemantic(diffs);
}
return diffs;
}
JS
# @private
DMP_JS_SNAPSHOT = MiniRacer::Snapshot
.new(File.read("#{__dir__}/diff_match_patch.min.js") + SCRIPT)
.warmup!("new diff_match_patch();")
def diff(text_old, text_new, semantic_clean: true, normalize_nl: true)
ctx = MiniRacer::Context.new(snapshot: DMP_JS_SNAPSHOT, timeout: 2_500)
diffs = ctx.call(
"dmp",
(normalize_nl ? text_old.gsub(NL_REGEXP, LF) : text_old),
(normalize_nl ? text_new.gsub(NL_REGEXP, LF) : text_new),
semantic_clean
)
diffs.map { |diff| [CONVERSION[diff["0"]], diff["1"]] }
end
end
end
/**
* Diff Match and Patch
* Copyright 2018 The diff-match-patch Authors.
* https://github.com/google/diff-match-patch
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var diff_match_patch=function(){this.Diff_Timeout=1,this.Diff_EditCost=4,this.Match_Threshold=.5,this.Match_Distance=1e3,this.Patch_DeleteThreshold=.5,this.Patch_Margin=4,this.Match_MaxBits=32},DIFF_DELETE=-1,DIFF_INSERT=1,DIFF_EQUAL=0;diff_match_patch.Diff=function(t,i){this[0]=t,this[1]=i},diff_match_patch.Diff.prototype.length=2,diff_match_patch.Diff.prototype.toString=function(){return this[0]+","+this[1]},diff_match_patch.prototype.diff_main=function(t,i,h,f){void 0===f&&(f=this.Diff_Timeout<=0?Number.MAX_VALUE:(new Date).getTime()+1e3*this.Diff_Timeout);var n=f;if(null==t||null==i)throw new Error("Null input. (diff_main)");if(t==i)return t?[new diff_match_patch.Diff(DIFF_EQUAL,t)]:[];void 0===h&&(h=!0);var e=h,a=this.diff_commonPrefix(t,i),r=t.substring(0,a);t=t.substring(a),i=i.substring(a),a=this.diff_commonSuffix(t,i);var s=t.substring(t.length-a);t=t.substring(0,t.length-a),i=i.substring(0,i.length-a);var _=this.diff_compute_(t,i,e,n);return r&&_.unshift(new diff_match_patch.Diff(DIFF_EQUAL,r)),s&&_.push(new diff_match_patch.Diff(DIFF_EQUAL,s)),this.diff_cleanupMerge(_),_},diff_match_patch.prototype.diff_compute_=function(t,i,h,f){var n;if(!t)return[new diff_match_patch.Diff(DIFF_INSERT,i)];if(!i)return[new diff_match_patch.Diff(DIFF_DELETE,t)];var e=t.length>i.length?t:i,a=t.length>i.length?i:t,r=e.indexOf(a);if(-1!=r)return n=[new diff_match_patch.Diff(DIFF_INSERT,e.substring(0,r)),new diff_match_patch.Diff(DIFF_EQUAL,a),new diff_match_patch.Diff(DIFF_INSERT,e.substring(r+a.length))],t.length>i.length&&(n[0][0]=n[2][0]=DIFF_DELETE),n;if(1==a.length)return[new diff_match_patch.Diff(DIFF_DELETE,t),new diff_match_patch.Diff(DIFF_INSERT,i)];var s=this.diff_halfMatch_(t,i);if(s){var _=s[0],c=s[1],l=s[2],g=s[3],o=s[4],p=this.diff_main(_,l,h,f),d=this.diff_main(c,g,h,f);return p.concat([new diff_match_patch.Diff(DIFF_EQUAL,o)],d)}return h&&t.length>100&&i.length>100?this.diff_lineMode_(t,i,f):this.diff_bisect_(t,i,f)},diff_match_patch.prototype.diff_lineMode_=function(t,i,h){var f=this.diff_linesToChars_(t,i);t=f.chars1,i=f.chars2;var n=f.lineArray,e=this.diff_main(t,i,!1,h);this.diff_charsToLines_(e,n),this.diff_cleanupSemantic(e),e.push(new diff_match_patch.Diff(DIFF_EQUAL,""));for(var a=0,r=0,s=0,_="",c="";a<e.length;){switch(e[a][0]){case DIFF_INSERT:s++,c+=e[a][1];break;case DIFF_DELETE:r++,_+=e[a][1];break;case DIFF_EQUAL:if(r>=1&&s>=1){e.splice(a-r-s,r+s),a=a-r-s;for(var l=this.diff_main(_,c,!1,h),g=l.length-1;g>=0;g--)e.splice(a,0,l[g]);a+=l.length}s=0,r=0,_="",c=""}a++}return e.pop(),e},diff_match_patch.prototype.diff_bisect_=function(t,i,h){for(var f=t.length,n=i.length,e=Math.ceil((f+n)/2),a=e,r=2*e,s=new Array(r),_=new Array(r),c=0;c<r;c++)s[c]=-1,_[c]=-1;s[a+1]=0,_[a+1]=0;for(var l=f-n,g=l%2!=0,o=0,p=0,d=0,u=0,m=0;m<e&&!((new Date).getTime()>h);m++){for(var F=-m+o;F<=m-p;F+=2){for(var D=a+F,E=(L=F==-m||F!=m&&s[D-1]<s[D+1]?s[D+1]:s[D-1]+1)-F;L<f&&E<n&&t.charAt(L)==i.charAt(E);)L++,E++;if(s[D]=L,L>f)p+=2;else if(E>n)o+=2;else if(g){if((v=a+l-F)>=0&&v<r&&-1!=_[v])if(L>=(b=f-_[v]))return this.diff_bisectSplit_(t,i,L,E,h)}}for(var I=-m+d;I<=m-u;I+=2){for(var b,v=a+I,w=(b=I==-m||I!=m&&_[v-1]<_[v+1]?_[v+1]:_[v-1]+1)-I;b<f&&w<n&&t.charAt(f-b-1)==i.charAt(n-w-1);)b++,w++;if(_[v]=b,b>f)u+=2;else if(w>n)d+=2;else if(!g){if((D=a+l-I)>=0&&D<r&&-1!=s[D]){var L;E=a+(L=s[D])-D;if(L>=(b=f-b))return this.diff_bisectSplit_(t,i,L,E,h)}}}}return[new diff_match_patch.Diff(DIFF_DELETE,t),new diff_match_patch.Diff(DIFF_INSERT,i)]},diff_match_patch.prototype.diff_bisectSplit_=function(t,i,h,f,n){var e=t.substring(0,h),a=i.substring(0,f),r=t.substring(h),s=i.substring(f),_=this.diff_main(e,a,!1,n),c=this.diff_main(r,s,!1,n);return _.concat(c)},diff_match_patch.prototype.diff_linesToChars_=function(t,i){var h=[],f={};function n(t){for(var i="",n=0,a=-1,r=h.length;a<t.length-1;){-1==(a=t.indexOf("\n",n))&&(a=t.length-1);var s=t.substring(n,a+1);(f.hasOwnProperty?f.hasOwnProperty(s):void 0!==f[s])?i+=String.fromCharCode(f[s]):(r==e&&(s=t.substring(n),a=t.length),i+=String.fromCharCode(r),f[s]=r,h[r++]=s),n=a+1}return i}h[0]="";var e=4e4,a=n(t);return e=65535,{chars1:a,chars2:n(i),lineArray:h}},diff_match_patch.prototype.diff_charsToLines_=function(t,i){for(var h=0;h<t.length;h++){for(var f=t[h][1],n=[],e=0;e<f.length;e++)n[e]=i[f.charCodeAt(e)];t[h][1]=n.join("")}},diff_match_patch.prototype.diff_commonPrefix=function(t,i){if(!t||!i||t.charAt(0)!=i.charAt(0))return 0;for(var h=0,f=Math.min(t.length,i.length),n=f,e=0;h<n;)t.substring(e,n)==i.substring(e,n)?e=h=n:f=n,n=Math.floor((f-h)/2+h);return n},diff_match_patch.prototype.diff_commonSuffix=function(t,i){if(!t||!i||t.charAt(t.length-1)!=i.charAt(i.length-1))return 0;for(var h=0,f=Math.min(t.length,i.length),n=f,e=0;h<n;)t.substring(t.length-n,t.length-e)==i.substring(i.length-n,i.length-e)?e=h=n:f=n,n=Math.floor((f-h)/2+h);return n},diff_match_patch.prototype.diff_commonOverlap_=function(t,i){var h=t.length,f=i.length;if(0==h||0==f)return 0;h>f?t=t.substring(h-f):h<f&&(i=i.substring(0,h));var n=Math.min(h,f);if(t==i)return n;for(var e=0,a=1;;){var r=t.substring(n-a),s=i.indexOf(r);if(-1==s)return e;a+=s,0!=s&&t.substring(n-a)!=i.substring(0,a)||(e=a,a++)}},diff_match_patch.prototype.diff_halfMatch_=function(t,i){if(this.Diff_Timeout<=0)return null;var h=t.length>i.length?t:i,f=t.length>i.length?i:t;if(h.length<4||2*f.length<h.length)return null;var n=this;function e(t,i,h){for(var f,e,a,r,s=t.substring(h,h+Math.floor(t.length/4)),_=-1,c="";-1!=(_=i.indexOf(s,_+1));){var l=n.diff_commonPrefix(t.substring(h),i.substring(_)),g=n.diff_commonSuffix(t.substring(0,h),i.substring(0,_));c.length<g+l&&(c=i.substring(_-g,_)+i.substring(_,_+l),f=t.substring(0,h-g),e=t.substring(h+l),a=i.substring(0,_-g),r=i.substring(_+l))}return 2*c.length>=t.length?[f,e,a,r,c]:null}var a,r,s,_,c,l=e(h,f,Math.ceil(h.length/4)),g=e(h,f,Math.ceil(h.length/2));return l||g?(a=g?l&&l[4].length>g[4].length?l:g:l,t.length>i.length?(r=a[0],s=a[1],_=a[2],c=a[3]):(_=a[0],c=a[1],r=a[2],s=a[3]),[r,s,_,c,a[4]]):null},diff_match_patch.prototype.diff_cleanupSemantic=function(t){for(var i=!1,h=[],f=0,n=null,e=0,a=0,r=0,s=0,_=0;e<t.length;)t[e][0]==DIFF_EQUAL?(h[f++]=e,a=s,r=_,s=0,_=0,n=t[e][1]):(t[e][0]==DIFF_INSERT?s+=t[e][1].length:_+=t[e][1].length,n&&n.length<=Math.max(a,r)&&n.length<=Math.max(s,_)&&(t.splice(h[f-1],0,new diff_match_patch.Diff(DIFF_DELETE,n)),t[h[f-1]+1][0]=DIFF_INSERT,f--,e=--f>0?h[f-1]:-1,a=0,r=0,s=0,_=0,n=null,i=!0)),e++;for(i&&this.diff_cleanupMerge(t),this.diff_cleanupSemanticLossless(t),e=1;e<t.length;){if(t[e-1][0]==DIFF_DELETE&&t[e][0]==DIFF_INSERT){var c=t[e-1][1],l=t[e][1],g=this.diff_commonOverlap_(c,l),o=this.diff_commonOverlap_(l,c);g>=o?(g>=c.length/2||g>=l.length/2)&&(t.splice(e,0,new diff_match_patch.Diff(DIFF_EQUAL,l.substring(0,g))),t[e-1][1]=c.substring(0,c.length-g),t[e+1][1]=l.substring(g),e++):(o>=c.length/2||o>=l.length/2)&&(t.splice(e,0,new diff_match_patch.Diff(DIFF_EQUAL,c.substring(0,o))),t[e-1][0]=DIFF_INSERT,t[e-1][1]=l.substring(0,l.length-o),t[e+1][0]=DIFF_DELETE,t[e+1][1]=c.substring(o),e++),e++}e++}},diff_match_patch.prototype.diff_cleanupSemanticLossless=function(t){function i(t,i){if(!t||!i)return 6;var h=t.charAt(t.length-1),f=i.charAt(0),n=h.match(diff_match_patch.nonAlphaNumericRegex_),e=f.match(diff_match_patch.nonAlphaNumericRegex_),a=n&&h.match(diff_match_patch.whitespaceRegex_),r=e&&f.match(diff_match_patch.whitespaceRegex_),s=a&&h.match(diff_match_patch.linebreakRegex_),_=r&&f.match(diff_match_patch.linebreakRegex_),c=s&&t.match(diff_match_patch.blanklineEndRegex_),l=_&&i.match(diff_match_patch.blanklineStartRegex_);return c||l?5:s||_?4:n&&!a&&r?3:a||r?2:n||e?1:0}for(var h=1;h<t.length-1;){if(t[h-1][0]==DIFF_EQUAL&&t[h+1][0]==DIFF_EQUAL){var f=t[h-1][1],n=t[h][1],e=t[h+1][1],a=this.diff_commonSuffix(f,n);if(a){var r=n.substring(n.length-a);f=f.substring(0,f.length-a),n=r+n.substring(0,n.length-a),e=r+e}for(var s=f,_=n,c=e,l=i(f,n)+i(n,e);n.charAt(0)===e.charAt(0);){f+=n.charAt(0),n=n.substring(1)+e.charAt(0),e=e.substring(1);var g=i(f,n)+i(n,e);g>=l&&(l=g,s=f,_=n,c=e)}t[h-1][1]!=s&&(s?t[h-1][1]=s:(t.splice(h-1,1),h--),t[h][1]=_,c?t[h+1][1]=c:(t.splice(h+1,1),h--))}h++}},diff_match_patch.nonAlphaNumericRegex_=/[^a-zA-Z0-9]/,diff_match_patch.whitespaceRegex_=/\s/,diff_match_patch.linebreakRegex_=/[\r\n]/,diff_match_patch.blanklineEndRegex_=/\n\r?\n$/,diff_match_patch.blanklineStartRegex_=/^\r?\n\r?\n/,diff_match_patch.prototype.diff_cleanupEfficiency=function(t){for(var i=!1,h=[],f=0,n=null,e=0,a=!1,r=!1,s=!1,_=!1;e<t.length;)t[e][0]==DIFF_EQUAL?(t[e][1].length<this.Diff_EditCost&&(s||_)?(h[f++]=e,a=s,r=_,n=t[e][1]):(f=0,n=null),s=_=!1):(t[e][0]==DIFF_DELETE?_=!0:s=!0,n&&(a&&r&&s&&_||n.length<this.Diff_EditCost/2&&a+r+s+_==3)&&(t.splice(h[f-1],0,new diff_match_patch.Diff(DIFF_DELETE,n)),t[h[f-1]+1][0]=DIFF_INSERT,f--,n=null,a&&r?(s=_=!0,f=0):(e=--f>0?h[f-1]:-1,s=_=!1),i=!0)),e++;i&&this.diff_cleanupMerge(t)},diff_match_patch.prototype.diff_cleanupMerge=function(t){t.push(new diff_match_patch.Diff(DIFF_EQUAL,""));for(var i,h=0,f=0,n=0,e="",a="";h<t.length;)switch(t[h][0]){case DIFF_INSERT:n++,a+=t[h][1],h++;break;case DIFF_DELETE:f++,e+=t[h][1],h++;break;case DIFF_EQUAL:f+n>1?(0!==f&&0!==n&&(0!==(i=this.diff_commonPrefix(a,e))&&(h-f-n>0&&t[h-f-n-1][0]==DIFF_EQUAL?t[h-f-n-1][1]+=a.substring(0,i):(t.splice(0,0,new diff_match_patch.Diff(DIFF_EQUAL,a.substring(0,i))),h++),a=a.substring(i),e=e.substring(i)),0!==(i=this.diff_commonSuffix(a,e))&&(t[h][1]=a.substring(a.length-i)+t[h][1],a=a.substring(0,a.length-i),e=e.substring(0,e.length-i))),h-=f+n,t.splice(h,f+n),e.length&&(t.splice(h,0,new diff_match_patch.Diff(DIFF_DELETE,e)),h++),a.length&&(t.splice(h,0,new diff_match_patch.Diff(DIFF_INSERT,a)),h++),h++):0!==h&&t[h-1][0]==DIFF_EQUAL?(t[h-1][1]+=t[h][1],t.splice(h,1)):h++,n=0,f=0,e="",a=""}""===t[t.length-1][1]&&t.pop();var r=!1;for(h=1;h<t.length-1;)t[h-1][0]==DIFF_EQUAL&&t[h+1][0]==DIFF_EQUAL&&(t[h][1].substring(t[h][1].length-t[h-1][1].length)==t[h-1][1]?(t[h][1]=t[h-1][1]+t[h][1].substring(0,t[h][1].length-t[h-1][1].length),t[h+1][1]=t[h-1][1]+t[h+1][1],t.splice(h-1,1),r=!0):t[h][1].substring(0,t[h+1][1].length)==t[h+1][1]&&(t[h-1][1]+=t[h+1][1],t[h][1]=t[h][1].substring(t[h+1][1].length)+t[h+1][1],t.splice(h+1,1),r=!0)),h++;r&&this.diff_cleanupMerge(t)},diff_match_patch.prototype.diff_xIndex=function(t,i){var h,f=0,n=0,e=0,a=0;for(h=0;h<t.length&&(t[h][0]!==DIFF_INSERT&&(f+=t[h][1].length),t[h][0]!==DIFF_DELETE&&(n+=t[h][1].length),!(f>i));h++)e=f,a=n;return t.length!=h&&t[h][0]===DIFF_DELETE?a:a+(i-e)},diff_match_patch.prototype.diff_prettyHtml=function(t){for(var i=[],h=/&/g,f=/</g,n=/>/g,e=/\n/g,a=0;a<t.length;a++){var r=t[a][0],s=t[a][1].replace(h,"&amp;").replace(f,"&lt;").replace(n,"&gt;").replace(e,"&para;<br>");switch(r){case DIFF_INSERT:i[a]='<ins style="background:#e6ffe6;">'+s+"</ins>";break;case DIFF_DELETE:i[a]='<del style="background:#ffe6e6;">'+s+"</del>";break;case DIFF_EQUAL:i[a]="<span>"+s+"</span>"}}return i.join("")},diff_match_patch.prototype.diff_text1=function(t){for(var i=[],h=0;h<t.length;h++)t[h][0]!==DIFF_INSERT&&(i[h]=t[h][1]);return i.join("")},diff_match_patch.prototype.diff_text2=function(t){for(var i=[],h=0;h<t.length;h++)t[h][0]!==DIFF_DELETE&&(i[h]=t[h][1]);return i.join("")},diff_match_patch.prototype.diff_levenshtein=function(t){for(var i=0,h=0,f=0,n=0;n<t.length;n++){var e=t[n][0],a=t[n][1];switch(e){case DIFF_INSERT:h+=a.length;break;case DIFF_DELETE:f+=a.length;break;case DIFF_EQUAL:i+=Math.max(h,f),h=0,f=0}}return i+=Math.max(h,f)},diff_match_patch.prototype.diff_toDelta=function(t){for(var i=[],h=0;h<t.length;h++)switch(t[h][0]){case DIFF_INSERT:i[h]="+"+encodeURI(t[h][1]);break;case DIFF_DELETE:i[h]="-"+t[h][1].length;break;case DIFF_EQUAL:i[h]="="+t[h][1].length}return i.join("\t").replace(/%20/g," ")},diff_match_patch.prototype.diff_fromDelta=function(t,i){for(var h=[],f=0,n=0,e=i.split(/\t/g),a=0;a<e.length;a++){var r=e[a].substring(1);switch(e[a].charAt(0)){case"+":try{h[f++]=new diff_match_patch.Diff(DIFF_INSERT,decodeURI(r))}catch(t){throw new Error("Illegal escape in diff_fromDelta: "+r)}break;case"-":case"=":var s=parseInt(r,10);if(isNaN(s)||s<0)throw new Error("Invalid number in diff_fromDelta: "+r);var _=t.substring(n,n+=s);"="==e[a].charAt(0)?h[f++]=new diff_match_patch.Diff(DIFF_EQUAL,_):h[f++]=new diff_match_patch.Diff(DIFF_DELETE,_);break;default:if(e[a])throw new Error("Invalid diff operation in diff_fromDelta: "+e[a])}}if(n!=t.length)throw new Error("Delta length ("+n+") does not equal source text length ("+t.length+").");return h},diff_match_patch.prototype.match_main=function(t,i,h){if(null==t||null==i||null==h)throw new Error("Null input. (match_main)");return h=Math.max(0,Math.min(h,t.length)),t==i?0:t.length?t.substring(h,h+i.length)==i?h:this.match_bitap_(t,i,h):-1},diff_match_patch.prototype.match_bitap_=function(t,i,h){if(i.length>this.Match_MaxBits)throw new Error("Pattern too long for this browser.");var f=this.match_alphabet_(i),n=this;function e(t,f){var e=t/i.length,a=Math.abs(h-f);return n.Match_Distance?e+a/n.Match_Distance:a?1:e}var a=this.Match_Threshold,r=t.indexOf(i,h);-1!=r&&(a=Math.min(e(0,r),a),-1!=(r=t.lastIndexOf(i,h+i.length))&&(a=Math.min(e(0,r),a)));var s,_,c=1<<i.length-1;r=-1;for(var l,g=i.length+t.length,o=0;o<i.length;o++){for(s=0,_=g;s<_;)e(o,h+_)<=a?s=_:g=_,_=Math.floor((g-s)/2+s);g=_;var p=Math.max(1,h-_+1),d=Math.min(h+_,t.length)+i.length,u=Array(d+2);u[d+1]=(1<<o)-1;for(var m=d;m>=p;m--){var F=f[t.charAt(m-1)];if(u[m]=0===o?(u[m+1]<<1|1)&F:(u[m+1]<<1|1)&F|(l[m+1]|l[m])<<1|1|l[m+1],u[m]&c){var D=e(o,m-1);if(D<=a){if(a=D,!((r=m-1)>h))break;p=Math.max(1,2*h-r)}}}if(e(o+1,h)>a)break;l=u}return r},diff_match_patch.prototype.match_alphabet_=function(t){for(var i={},h=0;h<t.length;h++)i[t.charAt(h)]=0;for(h=0;h<t.length;h++)i[t.charAt(h)]|=1<<t.length-h-1;return i},diff_match_patch.prototype.patch_addContext_=function(t,i){if(0!=i.length){if(null===t.start2)throw Error("patch not initialized");for(var h=i.substring(t.start2,t.start2+t.length1),f=0;i.indexOf(h)!=i.lastIndexOf(h)&&h.length<this.Match_MaxBits-this.Patch_Margin-this.Patch_Margin;)f+=this.Patch_Margin,h=i.substring(t.start2-f,t.start2+t.length1+f);f+=this.Patch_Margin;var n=i.substring(t.start2-f,t.start2);n&&t.diffs.unshift(new diff_match_patch.Diff(DIFF_EQUAL,n));var e=i.substring(t.start2+t.length1,t.start2+t.length1+f);e&&t.diffs.push(new diff_match_patch.Diff(DIFF_EQUAL,e)),t.start1-=n.length,t.start2-=n.length,t.length1+=n.length+e.length,t.length2+=n.length+e.length}},diff_match_patch.prototype.patch_make=function(t,i,h){var f,n;if("string"==typeof t&&"string"==typeof i&&void 0===h)f=t,(n=this.diff_main(f,i,!0)).length>2&&(this.diff_cleanupSemantic(n),this.diff_cleanupEfficiency(n));else if(t&&"object"==typeof t&&void 0===i&&void 0===h)n=t,f=this.diff_text1(n);else if("string"==typeof t&&i&&"object"==typeof i&&void 0===h)f=t,n=i;else{if("string"!=typeof t||"string"!=typeof i||!h||"object"!=typeof h)throw new Error("Unknown call format to patch_make.");f=t,n=h}if(0===n.length)return[];for(var e=[],a=new diff_match_patch.patch_obj,r=0,s=0,_=0,c=f,l=f,g=0;g<n.length;g++){var o=n[g][0],p=n[g][1];switch(r||o===DIFF_EQUAL||(a.start1=s,a.start2=_),o){case DIFF_INSERT:a.diffs[r++]=n[g],a.length2+=p.length,l=l.substring(0,_)+p+l.substring(_);break;case DIFF_DELETE:a.length1+=p.length,a.diffs[r++]=n[g],l=l.substring(0,_)+l.substring(_+p.length);break;case DIFF_EQUAL:p.length<=2*this.Patch_Margin&&r&&n.length!=g+1?(a.diffs[r++]=n[g],a.length1+=p.length,a.length2+=p.length):p.length>=2*this.Patch_Margin&&r&&(this.patch_addContext_(a,c),e.push(a),a=new diff_match_patch.patch_obj,r=0,c=l,s=_)}o!==DIFF_INSERT&&(s+=p.length),o!==DIFF_DELETE&&(_+=p.length)}return r&&(this.patch_addContext_(a,c),e.push(a)),e},diff_match_patch.prototype.patch_deepCopy=function(t){for(var i=[],h=0;h<t.length;h++){var f=t[h],n=new diff_match_patch.patch_obj;n.diffs=[];for(var e=0;e<f.diffs.length;e++)n.diffs[e]=new diff_match_patch.Diff(f.diffs[e][0],f.diffs[e][1]);n.start1=f.start1,n.start2=f.start2,n.length1=f.length1,n.length2=f.length2,i[h]=n}return i},diff_match_patch.prototype.patch_apply=function(t,i){if(0==t.length)return[i,[]];t=this.patch_deepCopy(t);var h=this.patch_addPadding(t);i=h+i+h,this.patch_splitMax(t);for(var f=0,n=[],e=0;e<t.length;e++){var a,r,s=t[e].start2+f,_=this.diff_text1(t[e].diffs),c=-1;if(_.length>this.Match_MaxBits?-1!=(a=this.match_main(i,_.substring(0,this.Match_MaxBits),s))&&(-1==(c=this.match_main(i,_.substring(_.length-this.Match_MaxBits),s+_.length-this.Match_MaxBits))||a>=c)&&(a=-1):a=this.match_main(i,_,s),-1==a)n[e]=!1,f-=t[e].length2-t[e].length1;else if(n[e]=!0,f=a-s,_==(r=-1==c?i.substring(a,a+_.length):i.substring(a,c+this.Match_MaxBits)))i=i.substring(0,a)+this.diff_text2(t[e].diffs)+i.substring(a+_.length);else{var l=this.diff_main(_,r,!1);if(_.length>this.Match_MaxBits&&this.diff_levenshtein(l)/_.length>this.Patch_DeleteThreshold)n[e]=!1;else{this.diff_cleanupSemanticLossless(l);for(var g,o=0,p=0;p<t[e].diffs.length;p++){var d=t[e].diffs[p];d[0]!==DIFF_EQUAL&&(g=this.diff_xIndex(l,o)),d[0]===DIFF_INSERT?i=i.substring(0,a+g)+d[1]+i.substring(a+g):d[0]===DIFF_DELETE&&(i=i.substring(0,a+g)+i.substring(a+this.diff_xIndex(l,o+d[1].length))),d[0]!==DIFF_DELETE&&(o+=d[1].length)}}}}return[i=i.substring(h.length,i.length-h.length),n]},diff_match_patch.prototype.patch_addPadding=function(t){for(var i=this.Patch_Margin,h="",f=1;f<=i;f++)h+=String.fromCharCode(f);for(f=0;f<t.length;f++)t[f].start1+=i,t[f].start2+=i;var n=t[0],e=n.diffs;if(0==e.length||e[0][0]!=DIFF_EQUAL)e.unshift(new diff_match_patch.Diff(DIFF_EQUAL,h)),n.start1-=i,n.start2-=i,n.length1+=i,n.length2+=i;else if(i>e[0][1].length){var a=i-e[0][1].length;e[0][1]=h.substring(e[0][1].length)+e[0][1],n.start1-=a,n.start2-=a,n.length1+=a,n.length2+=a}if(0==(e=(n=t[t.length-1]).diffs).length||e[e.length-1][0]!=DIFF_EQUAL)e.push(new diff_match_patch.Diff(DIFF_EQUAL,h)),n.length1+=i,n.length2+=i;else if(i>e[e.length-1][1].length){a=i-e[e.length-1][1].length;e[e.length-1][1]+=h.substring(0,a),n.length1+=a,n.length2+=a}return h},diff_match_patch.prototype.patch_splitMax=function(t){for(var i=this.Match_MaxBits,h=0;h<t.length;h++)if(!(t[h].length1<=i)){var f=t[h];t.splice(h--,1);for(var n=f.start1,e=f.start2,a="";0!==f.diffs.length;){var r=new diff_match_patch.patch_obj,s=!0;for(r.start1=n-a.length,r.start2=e-a.length,""!==a&&(r.length1=r.length2=a.length,r.diffs.push(new diff_match_patch.Diff(DIFF_EQUAL,a)));0!==f.diffs.length&&r.length1<i-this.Patch_Margin;){var _=f.diffs[0][0],c=f.diffs[0][1];_===DIFF_INSERT?(r.length2+=c.length,e+=c.length,r.diffs.push(f.diffs.shift()),s=!1):_===DIFF_DELETE&&1==r.diffs.length&&r.diffs[0][0]==DIFF_EQUAL&&c.length>2*i?(r.length1+=c.length,n+=c.length,s=!1,r.diffs.push(new diff_match_patch.Diff(_,c)),f.diffs.shift()):(c=c.substring(0,i-r.length1-this.Patch_Margin),r.length1+=c.length,n+=c.length,_===DIFF_EQUAL?(r.length2+=c.length,e+=c.length):s=!1,r.diffs.push(new diff_match_patch.Diff(_,c)),c==f.diffs[0][1]?f.diffs.shift():f.diffs[0][1]=f.diffs[0][1].substring(c.length))}a=(a=this.diff_text2(r.diffs)).substring(a.length-this.Patch_Margin);var l=this.diff_text1(f.diffs).substring(0,this.Patch_Margin);""!==l&&(r.length1+=l.length,r.length2+=l.length,0!==r.diffs.length&&r.diffs[r.diffs.length-1][0]===DIFF_EQUAL?r.diffs[r.diffs.length-1][1]+=l:r.diffs.push(new diff_match_patch.Diff(DIFF_EQUAL,l))),s||t.splice(++h,0,r)}}},diff_match_patch.prototype.patch_toText=function(t){for(var i=[],h=0;h<t.length;h++)i[h]=t[h];return i.join("")},diff_match_patch.prototype.patch_fromText=function(t){var i=[];if(!t)return i;for(var h=t.split("\n"),f=0,n=/^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@$/;f<h.length;){var e=h[f].match(n);if(!e)throw new Error("Invalid patch string: "+h[f]);var a=new diff_match_patch.patch_obj;for(i.push(a),a.start1=parseInt(e[1],10),""===e[2]?(a.start1--,a.length1=1):"0"==e[2]?a.length1=0:(a.start1--,a.length1=parseInt(e[2],10)),a.start2=parseInt(e[3],10),""===e[4]?(a.start2--,a.length2=1):"0"==e[4]?a.length2=0:(a.start2--,a.length2=parseInt(e[4],10)),f++;f<h.length;){var r=h[f].charAt(0);try{var s=decodeURI(h[f].substring(1))}catch(t){throw new Error("Illegal escape in patch_fromText: "+s)}if("-"==r)a.diffs.push(new diff_match_patch.Diff(DIFF_DELETE,s));else if("+"==r)a.diffs.push(new diff_match_patch.Diff(DIFF_INSERT,s));else if(" "==r)a.diffs.push(new diff_match_patch.Diff(DIFF_EQUAL,s));else{if("@"==r)break;if(""!==r)throw new Error('Invalid patch mode "'+r+'" in: '+s)}f++}}return i},diff_match_patch.patch_obj=function(){this.diffs=[],this.start1=null,this.start2=null,this.length1=0,this.length2=0},diff_match_patch.patch_obj.prototype.toString=function(){for(var t,i=["@@ -"+(0===this.length1?this.start1+",0":1==this.length1?this.start1+1:this.start1+1+","+this.length1)+" +"+(0===this.length2?this.start2+",0":1==this.length2?this.start2+1:this.start2+1+","+this.length2)+" @@\n"],h=0;h<this.diffs.length;h++){switch(this.diffs[h][0]){case DIFF_INSERT:t="+";break;case DIFF_DELETE:t="-";break;case DIFF_EQUAL:t=" "}i[h+1]=t+encodeURI(this.diffs[h][1])+"\n"}return i.join("").replace(/%20/g," ")},this.diff_match_patch=diff_match_patch,this.DIFF_DELETE=DIFF_DELETE,this.DIFF_INSERT=DIFF_INSERT,this.DIFF_EQUAL=DIFF_EQUAL;
# frozen_string_literal: true
module DMP
module Renderer
DIFF_HTMLS = {
DMP::Diff::DIFF_DELETE => ["<del>", "</del>"].freeze,
DMP::Diff::DIFF_INSERT => ["<ins>", "</ins>"].freeze,
DMP::Diff::DIFF_EQUAL => ["<span>", "</span>"].freeze
}.freeze
ROW_CLASSES = {
DMP::Diff::DIFF_EQUAL => "dmp-diff-unchanged",
DMP::Diff::DIFF_DELETE => "dmp-diff-deletion",
DMP::Diff::DIFF_INSERT => "dmp-diff-addition",
(DMP::Diff::DIFF_INSERT | DMP::Diff::DIFF_DELETE) => "dmp-diff-changed"
}.freeze
NL_REGEXP = /(\r\n|\n|\r)/
LF = "\n"
BR = "<br>"
WRAPPER_HTML_CLASS = "dmp-diff"
NB_CELL_HTML_CLASS = "dmp-diff-line"
TEXT_CELL_HTML_CLASS = "dmp-diff-text"
# Generates a html table with "td"s for line numbers and text
# @param diffs [Array<Integer,String>] DMP diff
def pretty_html_table(diffs)
open_line = nil
line_changes = 0
line_number = 0
content = +"<table class=\"#{WRAPPER_HTML_CLASS}\"><tbody>#{LF}"
diffs.each_with_index.inject(content) do |buff, (diff, diff_idx)|
mode = diff[0]
text = CGI.escapeHTML(diff[1])
open_html, close_html = DIFF_HTMLS[mode]
chunk_lines = text.lines
chunk_html = chunk_lines.each_with_index.each_with_object(+"") { |(line, chunk_idx), acc|
l = line.chomp
line_changes |= mode # bitwise or!
open_line ||= +""
open_line << "#{open_html}#{l}#{close_html}"
# reached line end or last line of last diff, flush line buffer into content
if line.end_with?(LF) || (chunk_lines.size - 1 == chunk_idx && diffs.size - 1 == diff_idx)
line_number += 1
tr_class = ROW_CLASSES[line_changes]
acc << <<~HTML.chop
<tr class="#{tr_class}"><td class="#{NB_CELL_HTML_CLASS}">#{line_number}</td><td class="#{TEXT_CELL_HTML_CLASS}">#{open_line}</td></tr>#{LF}
HTML
open_line = nil
line_changes = 0
end
acc
}
buff << chunk_html
end
content << "</tbody></table>"
end
# Each line in it's own div, all in one wrapper
# @param diffs [Array<Integer,String>] DMP diff
def pretty_html_divs(diffs)
open_line = nil
content = +"<div class=\"#{WRAPPER_HTML_CLASS}\">#{LF}"
diffs.each_with_index.inject(content) do |buff, (diff, diff_idx)|
mode = diff[0]
text = CGI.escapeHTML(diff[1])
open_html, close_html = DIFF_HTMLS[mode]
chunk_lines = text.lines
chunk_html = chunk_lines.each_with_index.each_with_object(+"") { |(line, chunk_idx), acc|
l = line.chomp
open_line ||= +""
open_line << "#{open_html}#{l}#{close_html}"
# reached line end or last line of last diff, flush line buffer into content
if line.end_with?(LF) || (chunk_lines.size - 1 == chunk_idx && diffs.size - 1 == diff_idx)
acc << <<~HTML.chop
<div class="#{TEXT_CELL_HTML_CLASS}">#{open_line}</div>#{LF}
HTML
open_line = nil
end
acc
}
buff << chunk_html
end
content << "</div>"
end
# All text in one wrapper
# @param diffs [Array<Integer,String>] DMP diff
# @param nl2br [Boolean|String] when sring is given, substitute new lines with that staring
def pretty_html_simple(diffs, nl2br: true)
diffs.inject(+"") do |buff, diff|
mode = diff[0]
text = CGI.escapeHTML(diff[1])
text.gsub!(NL_REGEXP, nl2br === true ? BR : nl2br) if nl2br # rubocop:disable Style/CaseEquality
open_html, close_html = DIFF_HTMLS[mode]
buff << "<div class=\"#{WRAPPER_HTML_CLASS}\">#{open_html}#{text}#{close_html}</div>"
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment