Применение diff-patch к строке / файлу - PullRequest
5 голосов
/ 16 апреля 2011

Для приложения для смартфонов с автономным режимом я создаю однонаправленную синхронизацию текста для файлов XML.Я бы хотел, чтобы мой сервер отправлял дельту / разницу (например, 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 ]

6 голосов
/ 16 апреля 2011

Ваша первая задача - выбрать формат патча.Самый сложный формат для чтения людьми (IMHO) оказывается самым простым форматом для программного обеспечения: скрипт 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 , все, что вам нужно сделать, это загрузить целевой файл в массив (по одному элементу в строке), проанализируйте патч, используя простой конечный автомат, вызовите Array # insert , чтобы добавить новые строки, и Array # delete_at , чтобы удалить их.Для написания патчера не нужно более пары десятков строк 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 для создания diff-файлов между двумя массивами (для начала google "rubygorithm :: diff").Объединение библиотеки diff с анализатором XML позволит вам создавать патчи, основанные на тегах, а не на строках, и это может подойти вам лучше.Важным моментом является выбор форматов патчей: как только вы выберете формат ed (и поймете, как правильно работает патч снизу вверх), все остальное в значительной степени становится на свои места без особых усилий.

2 голосов
/ 06 февраля 2016

Я знаю, что этому вопросу почти пять лет, но я все равно отправлю ответ.При поиске того, как создавать и применять исправления для строк в Ruby, даже сейчас я не смог найти ни одного ресурса, который бы удовлетворительно отвечал на этот вопрос.По этой причине я покажу, как я решил эту проблему в своем приложении.

Создание исправлений

Я предполагаю, что вы используете Linux, иначедоступ к программе diff через Cygwin.В этом случае вы можете использовать отличный Diffy gem для создания ed script патчей:

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

Применение патчей

Применение патчей не так просто.Я решил написать свой собственный алгоритм, , попросить улучшения в Code Review и, наконец, остановился на коде ниже.Этот код идентичен ответу 200_success , за исключением одного изменения для повышения его правильности.

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
...