将diff-patch应用于字符串/文件

时间:2011-04-16 16:04:58

标签: ruby merge diff smartphone

对于支持离线功能的智能手机应用,我正在为Xml文件创建单向文本同步。我希望我的服务器将delta /差异(例如GNU diff-patch)发送到目标设备。

这是计划:

Time = 0
Server: has version_1 of Xml file (~800 kiB)
Client: has version_1 of Xml file (~800 kiB)

Time = 1
Server: has version_1 and version_2 of Xml file (each ~800 kiB)
        computes delta of these versions (=patch) (~10 kiB) 
        sends patch to Client (~10 kiB transferred)

Client: computes version_2 from version_1 and patch  <= this is the problem =>

是否有一个Ruby库可以执行此操作以将文本修补程序应用于文件/字符串?可以根据库的要求格式化补丁。

感谢您的帮助!

(我正在使用Rhodes跨平台框架,它使用Ruby作为编程语言。)

2 个答案:

答案 0 :(得分:6)

您的首要任务是选择补丁格式。人类最难阅读的格式(恕我直言)证明是最容易应用的软件格式: ed (1)脚本。您可以从简单的/usr/bin/diff -e old.xml new.xml开始生成补丁; diff (1)将生成面向行的补丁,但开始时应该没问题。 ed格式如下所示:

36a
    <tr><td class="eg" style="background: #182349;">&nbsp;</td><td><tt>#182349</tt></td></tr>
.
34c
    <tr><td class="eg" style="background: #66ccff;">&nbsp;</td><td><tt>#xxxxxx</tt></td></tr>
.
20,23d

数字是行号,行号范围用逗号分隔。然后有三个单字母命令:

  • a :在此位置添加下一个文本块。
  • c :将此位置的文字更改为以下栏。这相当于 d ,后跟 a 命令。
  • d :删除这些行。

您还会注意到补丁中的行号是自下而上的,因此您不必担心更改会在补丁的后续块中弄乱行号。要添加或更改的实际文本块遵循命令,作为由具有单个句点的行终止的一系列行(即/^\.$/patch_line == '.',具体取决于您的偏好)。总之,格式如下:

[line-number-range][command]
[optional-argument-lines...]
[dot-terminator-if-there-are-arguments]

因此,要应用 ed 补丁,您需要做的就是将目标文件加载到一个数组中(每行一个元素),使用一个简单的状态机解析补丁,调用{{ 3}}添加新行,Array#insert删除它们。不应该使用超过几十行的Ruby来编写修补程序,并且不需要库。

如果您可以安排您的XML出现如下:

<tag>
blah blah
</tag>
<other-tag x="y">
mumble mumble
</other>

而不是:

<tag>blah blah</tag><other-tag x="y">mumble mumble</other>

那么上面简单的面向行的方法就可以了;额外的EOL不会占用太多空间,因此可以轻松实现。

有两个用于在两个数组之间产生差异的Ruby库(谷歌“ruby algorithm :: diff”开始)。将diff库与XML解析器结合使用,可以生成基于标记而非基于行的补丁,这可能更适合您。重要的是选择补丁格式,一旦你选择 ed 格式(并实现从底部到顶部的补丁的智慧),那么其他一切几乎都会付出很少的努力

答案 1 :(得分:2)

我知道这个问题已经快五年了,但我还是会发一个答案。当在Ruby中搜索如何为字符串制作和应用补丁时,即使是现在,我也无法找到满意地回答这个问题的任何资源。出于这个原因,我将展示如何在我的应用程序中解决这个问题。

制作补丁

我假设你正在使用Linux,或者通过Cygwin访问程序diff。在这种情况下,您可以使用优秀的Diffy gem来创建ed script补丁:

patch_text = Diffy::Diff.new(old_text, new_text, :diff => "-e").to_s

应用补丁

应用补丁并不是那么简单。我选择编写自己的算法ask for improvements in Code Review,最后决定使用下面的代码。此代码与200_success's answer相同,除了一项改进以提高其正确性。

require 'stringio'
def self.apply_patch(old_text, patch)
  text = old_text.split("\n")
  patch = StringIO.new(patch)
  current_line = 1

  while patch_line = patch.gets
    # Grab the command
    m = %r{\A(?:(\d+))?(?:,(\d+))?([acd]|s/\.//)\Z}.match(patch_line)
    raise ArgumentError.new("Invalid ed command: #{patch_line.chomp}") if m.nil?
    first_line = (m[1] || current_line).to_i
    last_line = (m[2] || first_line).to_i
    command = m[3]

    case command
    when "s/.//"
      (first_line..last_line).each { |i| text[i - 1].sub!(/./, '') }
    else
      if ['d', 'c'].include?(command)
        text[first_line - 1 .. last_line - 1] = []
      end
      if ['a', 'c'].include?(command)
        current_line = first_line - (command=='a' ? 0 : 1) # Adds are 0-indexed, but Changes and Deletes are 1-indexed
        while (patch_line = patch.gets) && (patch_line.chomp! != '.') && (patch_line != '.')
          text.insert(current_line, patch_line)
          current_line += 1
        end
      end
    end
  end
  text.join("\n")
end