znerol / node-delta

Delta.js - A JavaScript diff and patch engine for DOM trees

Home Page:http://znerol.github.com/node-delta/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Live updates?

marcelklehr opened this issue · comments

Hey, I came across this library while searching for a solution for diffing and patching a DOM tree live in the browser. (something like this: http://pomax.github.io/DOM-diff/)

Since there's no description in your README i am not sure what's the intended use case for your lib, though. Could you explain?

Yes, this project is similar. Though the goal here was to produce deltas with context. I.e. using node-delta it is possible to apply patches to a slightly modified version of the original DOM-tree. This is much like the GNU command line diff and patch tools work.

You find some demos and the whole project documentation on http://znerol.github.io/node-delta/

So, you can do a three-way merge, i guess?

Is there some real world example available?

So, you can do a three-way merge, i guess?

It would be possible to build a three-way-merge tool atop the algorithm. However node-delta only helps with conflict detection but more elaborate merging strategies are not implemented.

Is there some real world example available?

Regrettably no. I did not advance the project after I delivered my thesis.

Hey,
two small questions before looking at making such a real-world example:

  1. I notice it is set up for node.js usage. Would there be anything principally that prevent it from being used in the browser instead? (also speedwise)
  2. I notice it is set up to handle XML. Would it be possibvle to make it work with HTML5 as well? (given that HTML5 isn't always XML-compliant)

I notice it is set up for node.js usage. Would there be anything principally that prevent it from being used in the browser instead? (also speedwise)

One of the project goals was to write cross-platform code, node-delta runs on nodejs as well as in the browser (through browserify). For example you can build/start the test suite with make test in node and with make browser-test in the browser (currently hard wired to firefox). Have a look into the Makefile for some more hints.

I notice it is set up to handle XML. Would it be possibvle to make it work with HTML5 as well? (given that HTML5 isn't always XML-compliant)

Parsing and serialization is offloaded through xmlshim to either the browsers native XML parser/serializer or to libxmljs and jsdom when running under nodejs. The browser should be capable of parsing malformed HTML, I don't know whether libxmljs will digest that though.

Ok, thanks! I know this isn't perfect HTML handeling, but given that I work with quite controlled HTML-code, I believe I have figured out how to handle it.

I copy this file into the lib directory of node-delta, and create a delta.js by runnign browserify over it. The only thing I cannot quite figure out is how to do the unpatch function. Of course, I could when creating the diff just mke sure to also make a reverse diff that could then be used to do a reverse diff, but it would mean using double the space for a diff object. I have looked through the classes and couldn't find anything to turn a diff into an undiff. So my question is: is there some built-in way of doing this?

// DIFF
var delta = {};

var diffcmd = require('./delta/diff');
var diffmod = require('./delta/diff-skelmatch-factory');
var docmod = require('./delta/doc-xml-factory');
var deltamod = require('./delta/delta-xml-factory');

var difffact = new diffmod.DiffSkelmatchFactory();
var docfact = new docmod.DocumentXMLFactory();
var deltafact = new deltamod.DeltaXMLFactory();

var htmlToXhtml = function (htmlString) {
        htmlString = htmlString.replace(/<br>/g, "<br />");
        htmlString = htmlString.replace(/(<img[^>]+)>/gm, "$1 />");
        return htmlString;
};

var xhtmlToHtml = function (xhtmlString) {
        xhtmlString = xhtmlString.replace(/<br \/>/g, "<br>");
        xhtmlString = xhtmlString.replace(/(<img[^\/]+)\//gm, "$1");
        return xhtmlString;
};


delta.Diff = function(origcontent, changedcontent) {
    var doc1 = docfact.loadOriginalDocument(htmlToXhtml(origcontent));
    var doc2 = docfact.loadInputDocument(changedcontent);

    var d = new diffcmd.Diff(difffact, docfact, deltafact);
    var deltadoc = d.diff(doc1, doc2);

    return deltafact.serializeDocument(deltadoc);
};

// PATCH 
var resolverProfile = require('./profiles/algo-resolve-xcc');
var docProfile = require('./profiles/doc-tree-xml');
var deltaProfile = require('./profiles/delta-tree-xml');
var patch = require('./delta/patch');

delta.Patch = function(document_content, patch_content) {
    var p = new patch.Patch(resolverProfile, docProfile, deltaProfile);

    var doc = docProfile.loadOriginalDocument(document_content);
    var fragadapter = docProfile.createFragmentAdapter(doc.type);
    var delta = deltaProfile.loadDocument(patch_content, fragadapter);

    p.patch(doc, delta);

return xhtmlToHtml(docProfile.serializeDocument(doc));
};

// UNPATCH (NOT WORKING!)

delta.Unpatch = function(document_content, unpatch_content) {
    var patch_content = unpatch_content;
        patch_content = patch_content.replace(/<insert>/g, "<ninsert>");
        patch_content = patch_content.replace(/<\/insert>/g, "<\/ninsert>");
        patch_content = patch_content.replace(/<remove>/g, "<insert>");
        patch_content = patch_content.replace(/<\/remove>/g, "<\/insert>");
        patch_content = patch_content.replace(/<ninsert>/g, "<remove>");
        patch_content = patch_content.replace(/<\/ninsert>/g, "<\/remove>");
    return delta.Patch(document_content, patch_content);
};

window.delta = delta;

I believe that deltaProfile.loadDocument() returns an array of DetachedContextOperation objects.

What happens if you try to reverse the delta by simply constructing a new list of operations with all insert and remove properties swapped? E.g.

var reverse_delta = [];
delta.forEach(function(op) {
  var reverse_delta.push(new DetachedContextOperation(op.type, op.path, op.insert, op.remove, op.head, op.tail));
});

ah, thanks! This seems to work. I have attached the entire little script I use here. Feel free to adjust and oncorporate in your program. Providing a simple file that one cna just add to a web browser may mean that it attracts more people as well.

/* Simple Delta.js for browsers. 
 * License: MIT
 * 
 * Compile by copying to lib directory of node-delta and running browserify THIS_FILENAME.js > OUTPUT_FILENAME.js
 * 
 * Provides:
 * 
 * delta.Diff(html-string-1, html-string-2):
 * OUTPUT - a diff as a string.
 * 
 * delta.Patch(html-string, diff)
 * OUTPUT - the html-string with the diff applied as a patch
 * 
 * delta.Unpatch(html-string,diff)
 * OUTPUT - the html-string with the diff removed as a patch
 * 
*/

// DIFF
var delta = {};

var diffcmd = require('./delta/diff');
var diffmod = require('./delta/diff-skelmatch-factory');
var docmod = require('./delta/doc-xml-factory');
var deltamod = require('./delta/delta-xml-factory');

var difffact = new diffmod.DiffSkelmatchFactory();
var docfact = new docmod.DocumentXMLFactory();
var deltafact = new deltamod.DeltaXMLFactory();

var htmlToXhtml = function (htmlString) {
        htmlString = htmlString.replace(/<br>/g, "<br />");
        htmlString = htmlString.replace(/(<img[^>]+)>/gm, "$1 />");
        return htmlString;
};

var xhtmlToHtml = function (xhtmlString) {
        xhtmlString = xhtmlString.replace(/<br \/>/g, "<br>");
        xhtmlString = xhtmlString.replace(/(<img[^\/]+)\//gm, "$1");
        return xhtmlString;
};


delta.Diff = function(origcontent, changedcontent) {
    var doc1 = docfact.loadOriginalDocument(htmlToXhtml(origcontent));
    var doc2 = docfact.loadInputDocument(changedcontent);

    var d = new diffcmd.Diff(difffact, docfact, deltafact);
    var deltadoc = d.diff(doc1, doc2);

    return deltafact.serializeDocument(deltadoc);
};

// PATCH 
var resolverProfile = require('./profiles/algo-resolve-xcc');
var docProfile = require('./profiles/doc-tree-xml');
var deltaProfile = require('./profiles/delta-tree-xml');
var patch = require('./delta/patch');

delta.Patch = function(document_content, patch_content) {
    var p = new patch.Patch(resolverProfile, docProfile, deltaProfile);
    var doc = docProfile.loadOriginalDocument(document_content);
    var fragadapter = docProfile.createFragmentAdapter(doc.type);
    var delta = deltaProfile.loadDocument(patch_content, fragadapter);

    p.patch(doc, delta);

return xhtmlToHtml(docProfile.serializeDocument(doc));
};

// UNPATCH

var contextdelta = require('./delta/contextdelta');

delta.Unpatch = function(document_content, unpatch_content) {

    var p = new patch.Patch(resolverProfile, docProfile, deltaProfile);
    var doc = docProfile.loadOriginalDocument(document_content);
    var fragadapter = docProfile.createFragmentAdapter(doc.type);
    var delta = deltaProfile.loadDocument(unpatch_content, fragadapter);
    var reverse_delta_detached = [];

    delta.detached.forEach(function(op) {
        reverse_delta_detached.push(new contextdelta.DetachedContextOperation(op.type, op.path, op.insert, op.remove, op.head, op.tail));
    });

    delta.detached = reverse_delta_detached;

    p.patch(doc, delta);

    return xhtmlToHtml(docProfile.serializeDocument(doc));
};

window.delta = delta;