从两个字符串中抓取编辑

时间:2015-06-07 00:51:24

标签: javascript file storage edit safari-extension

我将深入探讨我的问题,您可以跳转到TL;如果您不想阅读所有这些,则可以使用DR

我正在尝试做什么

我需要存储一个“文件”(文本文档),可以由用户编辑。如果我有原始文件(可能很大)

  

Lorem ipsum dolor sit amet

并且用户要进行更改:

  

Foo ipsum amet_ sit

基本上,我有原始字符串和用户编辑的字符串。我想找到差异,“edits”。防止存储非常大的字符串的重复项。我想存储原始和“编辑”。然后将编辑应用于原始。有点像重复数据删除。问题是我不知道如何进行不同的编辑,我还需要能够将这些编辑应用于字符串。

的尝试

因为文本可能很大,我想知道如何在不存储两个单独版本的情况下将编辑存储到文本的最“有效”方式。我的第一个猜测是:

var str = 'Original String of text...'.split(' ') || [],
    mod = 'Modified String of text...'.split(' ') || [], i, edits = [];

for (i = 0; i < str.length; i += 1) {
    edits.push(str[i]===mod[i] ? undefined : mod[i]);
}

console.log(edits); // ["Modified", null, null, null] (desired output)

然后回复:

for (i = 0; i < str.length; i += 1) {
    str[i] = edits[i] || str[i];
}
str.join(' '); // "Modified String of text..."
  

基本上,我正在尝试将空格分成数组。比较阵列并存储差异。然后应用差异生成修改后的版本

问题

但是如果要改变空间的数量,就会出现问题:

str Original String of text... mod OriginalString of text...

输出:OriginalString of text... text...

我想要的输出:OriginalString of text...

即使我要将str.lengthmod.lengthedits.length切换为:

// Get edits
var str = 'Original String of text...'.split(' ') || [],
    mod = 'Modified String of text...'.split(' ') || [], i, edits = [];

for (i = 0; i < mod.length; i += 1) {
    edits.push(str[i]===mod[i] ? undefined : mod[i]);
}

// Apply edits
var final = [];
for (i = 0; i < edits.length; i += 1) {
    final[i] = edits[i] || str[i];
}
final = final.join(' ');

edits会:["ModifiedString", "of", "text..."]结果使整个'存储编辑事情变得毫无用处。更糟糕的是,如果要添加/删除一个单词。如果str成为Original String of lots of text...。输出仍然是相同的。

我可以看到他们在做这件事的方式上存在很多缺陷,但我想不出其他任何方式。

段:

document.getElementById('go').onclick = function() {
  var str = document.getElementById('a').value.split(' ') || [],
    mod = document.getElementById('b').value.split(' ') || [],
    i, edits = [];

  for (i = 0; i < mod.length; i += 1) {
    edits.push(str[i] === mod[i] ? undefined : mod[i]);
  }

  // Apply edits
  var final = [];
  for (i = 0; i < edits.length; i += 1) {
    final[i] = edits[i] || str[i];
  }
  final = final.join(' ');
  alert(final);
};

document.getElementById('go2').onclick = function() {
  var str = document.getElementById('a').value.split(' ') || [],
    mod = document.getElementById('b').value.split(' ') || [],
    i, edits = [];

  for (i = 0; i < str.length; i += 1) {
    edits.push(str[i] === mod[i] ? undefined : mod[i]);
  }

  for (i = 0; i < str.length; i += 1) {
    str[i] = edits[i] || str[i];
  }
  alert(str.join(' ')); // "Modified String of text..."
};
Base String:
<input id="a">
<br/>Modified String:
<input id="b" />
<br/>
<button id="go">Second method</button>
<button id="go2">First Method</button>

TL; DR:

您如何找到两个字符串之间的变化?

我正在处理大块文本,每个文本大约是兆字节百字节。这是在浏览器上运行

4 个答案:

答案 0 :(得分:3)

编辑:添加了可以处理多个文本区域的已修改脚本。

Here is the JSFiddle表示包含多个可编辑文本区域的页面。 (不要忘记打开开发工具来查看编辑。)您只需要为每个textarea分配一个唯一的ID。然后,使用这些id作为键创建地图,并将每个textarea的编辑数组作为值。这是更新的脚本:

'use strict';

function Edit(type, position, text) {
  this.type = type;
  this.position = position;
  this.text = text;
}

var ADD = 'add';
var DELETE = 'delete';

var textAreaEditsMap = {};

var cursorStart = -1;
var cursorEnd = -1;
var currentEdit = null;
var deleteOffset = 1;

window.addEventListener('load', function() {
  var textareas = document.getElementsByClassName('text-editable');

  for (var i = 0; i < textareas.length; ++i) {
    var textarea = textareas.item(i);
    var id = textarea.getAttribute('id');

    textAreaEditsMap[id] = [];
    textarea.addEventListener('mouseup', handleMouseUp);
    textarea.addEventListener('keydown', handleKeyDown);
    textarea.addEventListener('keypress', handleKeyPress);
  }
});

function handleMouseUp(event) {
  cursorStart = this.selectionStart;
  cursorEnd = this.selectionEnd;
  currentEdit = null;
}

function handleKeyDown(event) {

  cursorStart = this.selectionStart;
  cursorEnd = this.selectionEnd;

  if (event.keyCode >= 35 && event.keyCode <= 40) { // detect cursor movement keys
    currentEdit = null;
  }

  // deleting text
  if (event.keyCode === 8 || event.keyCode === 46) {
    if (currentEdit != null && currentEdit.type !== 'delete') {
      currentEdit = null;
    }

    if (cursorStart !== cursorEnd) { // Deleting highlighted text
      var edit = new Edit(DELETE, cursorStart, this.innerHTML.substring(cursorStart, cursorEnd));
      textAreaEditsMap[this.getAttribute('id')].push(edit);
      currentEdit = null;

    } else if (event.keyCode === 8) { // backspace
      if (currentEdit == null) {
        deleteOffset = 1;
        var edit = new Edit(DELETE, cursorStart, this.innerHTML[cursorStart - 1]);
        textAreaEditsMap[this.getAttribute('id')].push(edit);
        currentEdit = edit;
      } else {
        ++deleteOffset;
        currentEdit.text = this.innerHTML[cursorStart - 1] + currentEdit.text;
      }

    } else if (event.keyCode === 46) { // delete
      if (currentEdit == null) {
        deleteOffset = 1;
        var edit = new Edit(DELETE, cursorStart, this.innerHTML[cursorStart]);
        textAreaEditsMap[this.getAttribute('id')].push(edit);
        currentEdit = edit;

      } else {
        currentEdit.text += this.innerHTML[cursorStart + deleteOffset++];
      }
    }
  }

  console.log(textAreaEditsMap)
}

function handleKeyPress(event) {

  if (currentEdit != null && currentEdit.type !== 'add') {
    currentEdit = null;
  }

  if (currentEdit == null) {
    currentEdit = new Edit(ADD, cursorStart, String.fromCharCode(event.charCode));
    textAreaEditsMap[this.getAttribute('id')].push(currentEdit);
  } else {
    currentEdit.text += String.fromCharCode(event.charCode);
  }

  console.log(textAreaEditsMap);
}

原始帖子的原始帖子仅处理一个textarea:

我制作了一个示例脚本,可以满足您的需求。我在JSFiddle上放了一个working example。确保在JSFiddle示例页面上按ctrl + shift + J打开开发工具,这样您就可以看到在编辑时记录的编辑数组。编辑按时间顺序添加到编辑数组中,因此您可以通过以反向时间顺序应用反向(即,添加删除的文本;删除添加的文本)(即,向后迭代数组)来恢复到原始文本。我没有从上下文菜单或通过键绑定处理复制,粘贴,撤消或重做,但我认为您应该能够使用此示例作为指导来处理这些事情。这是脚本:

'use strict';

function Edit(type, position, text) {
  this.type = type;
  this.position = position;
  this.text = text;
}

window.addEventListener('load', function() {
  var ADD = 'add';
  var DELETE = 'delete';

  var cursorStart = -1;
  var cursorEnd = -1;
  var edits = [];
  var currentEdit = null;
  var deleteOffset = 1;

  var textarea = document.getElementById('saved-text');

  textarea.addEventListener('mouseup', function(event) {
    cursorStart = this.selectionStart;
    cursorEnd = this.selectionEnd;
    currentEdit = null;
  });

  textarea.addEventListener('keydown', function(event) {

    cursorStart = this.selectionStart;
    cursorEnd = this.selectionEnd;

    if(event.keyCode >= 35 && event.keyCode <= 40) { // detect cursor movement keys
      currentEdit = null;
    }

    // deleting text
    if(event.keyCode === 8 || event.keyCode === 46) {
      if(currentEdit != null && currentEdit.type !== 'delete') {
        currentEdit = null;
      }

      if(cursorStart !== cursorEnd) {
        var edit = new Edit(DELETE, cursorStart, textarea.innerHTML.substring(cursorStart, cursorEnd));
        edits.push(edit);
        currentEdit = null;

      } else if (event.keyCode === 8) { // backspace
        if (currentEdit == null) {
          deleteOffset = 1;
          var edit = new Edit(DELETE, cursorStart, textarea.innerHTML[cursorStart - 1]);
          edits.push(edit);
          currentEdit = edit;
        } else {
          ++deleteOffset;
          currentEdit.text = textarea.innerHTML[cursorStart - 1] + currentEdit.text;
        }

      } else if (event.keyCode === 46) { // delete
        if(currentEdit == null) {
          deleteOffset = 1;
          var edit = new Edit(DELETE, cursorStart, textarea.innerHTML[cursorStart]);
          edits.push(edit);
          currentEdit = edit;

        } else {
          currentEdit.text += textarea.innerHTML[cursorStart + deleteOffset++];
        }
      }
    }

    console.log(edits)
  });

  textarea.addEventListener('keypress', function(event) {

    if(currentEdit != null && currentEdit.type !== 'add') {
      currentEdit = null;
    }

    // adding text
    if(currentEdit == null) {
      currentEdit = new Edit(ADD, cursorStart, String.fromCharCode(event.charCode));
      edits.push(currentEdit);
    } else {
      currentEdit.text += String.fromCharCode(event.charCode);
    }

    console.log(edits);
  });

});

答案 1 :(得分:3)

这是一个类似于代码版本控制的问题,只保存版本之间的更改。

查看jsdiff

您可以创建一个补丁,保存它,然后将其应用到原始文本中以获取修改后的文本。

答案 2 :(得分:1)

使用JavaScript运行适当的差异可能会很慢,但这取决于性能要求和差异的质量,当然还有它必须运行的频率。

一种非常有效的方法是在用户实际编辑文档时跟踪编辑,并仅在完成后立即存储这些更改。为此,您可以使用ACE编辑器或任何其他支持更改跟踪的编辑器。

http://ace.c9.io/

ACE正在编辑文档时跟踪更改。 ACE编辑器以易于理解的格式跟踪命令,如:

{"action":"insertText","range":{"start":{"row":0,"column":0},
    "end":{"row":0,"column":1}},"text":"d"}

您可以挂钩ACE编辑器的更改并收听更改事件:

var changeList = []; // list of changes
// editor is here the ACE editor instance for example
var editor = ace.edit(document.getElementById("editorDivId"));
editor.setValue("original text contents");
editor.on("change", function(e) {
    // e.data has the change
    var cmd = e.data;
    var range = cmd.range;
    if(cmd.action=="insertText") {
        changeList.push([
            1, 
            range.start.row,
            range.start.column,
            range.end.row,
            range.end.column,
            cmd.text
        ])
    }
    if(cmd.action=="removeText") {
        changeList.push([
                2, 
                range.start.row,
                range.start.column,
                range.end.row,
                range.end.column,
                cmd.text
            ])
    }
    if(cmd.action=="insertLines") {
        changeList.push([
                3, 
                range.start.row,
                range.start.column,
                range.end.row,
                range.end.column,
                cmd.lines
            ])
    }
    if(cmd.action=="removeLines") {
        changeList.push([
                4, 
                range.start.row,
                range.start.column,
                range.end.row,
                range.end.column,
                cmd.lines,
                cmd.nl
            ])
    }
});

要了解它是如何工作的,只需创建一些捕获更改的测试运行。基本上只有命令的那些:

  1. insertText
  2. removeText
  3. insertLines
  4. removeLines
  5. 从文本中删除换行符可能有点棘手。

    如果您有此更改列表,则可以根据文本文件重播更改。您甚至可以将相似或重叠的更改合并到一个更改中 - 例如,插入到后续字符可以合并为单个更改。

    当你测试这个时会出现一些问题,将字符串组合回文本并不是一件容易的事情,但非常可行,不应超过大约100行代码。

    好消息是,当您完成后,您还可以轻松地使用撤消重做命令,这样您就可以重播整个编辑过程。

答案 3 :(得分:1)

尝试创建基本比较标识符,例如js "+"以下"-";利用.map()来比较原始o,已修改的e输入字符串,返回diffo之间差异的数组e;将oe,diff设置为对象的属性

&#13;
&#13;
var o = "Lorem ipsum dolor sit amet",
  e = "Foo ipsum amet_ sit"
, res = {
  "original": o,
  "edited": e,
  "diff": o.split("").map(function(val, key) {
    // log edits 
    // `+` preceding character: added character ,
    // `-`: preceding character: removed character;
    // `+` preceding "|": no changes ,
    // `-`: preceding "": no changes;
    // `"index"`: character `index` of original `o` input string
    return e[key] !== val 
           ? "[edits:" + "+" + (e[key] || "") + "|-" + val 
             + ", index:" + key + "]" + (e[key] || "") 
           : "[edits:+|-, index:" + key + "]" + val
  })
};

document.getElementsByTagName("pre")[0].textContent = JSON.stringify(res, null, 2);
&#13;
<pre></pre>
&#13;
&#13;
&#13;