ODSファイル同士の比較表示

TortoiseSVN向けにWinMergeを使ったODSファイルの差分を表示するスクリプトRubyで書いた。普段TortoiseSVNの「差分ビューアー」に登録する場合はexerbを使って実行ファイル化するんだけど、exerbを使えるRuby 1.8環境にはNokogiriをインストールしてなくて、かつ今更インストールするのも面倒。ということで、TortoiseSVNにはrubyw.exeでスクリプトを実行するように設定した。
ちなみに書いたのやこんなヤツ。以前Qiitaにあげたもの*1と作りは同じだけど、ODSファイルをTSV形式に変換するところを具体的に書いた。ODSファイルからcontent.xmlを取り出して、NokogiriのSAXパーサーを使ってTSV形式のファイルにしてからWinMergeで比較している。

require 'nokogiri'
require 'zip/zipfilesystem'

class Doc < Nokogiri::XML::SAX::Document
  attr_reader :tsv

  def initialize
    @tsv = ''
    @begin_of_row = false
    @str = ''
  end

  def characters(str)
    @str << str
  end

  def start_element(name, attrs = {})
    case name
    when 'table:table-row'
      @begin_of_row = true
    when 'table:table-cell'
      n = (attrs.assoc('table:number-columns-repeated') || [0, 1])[1].to_i
      n.times do
	if @begin_of_row
	  @begin_of_row = false
	else
	  @tsv << "\t"
	end
      end
    else
      @begin_of_row = false
    end
  end

  def end_element(name, attrs = {})
    case name
    when 'table:table-row'
      @tsv << "\n"
    when 'table:table-cell'
      @tsv << "\"#{@str}\"" unless @str.empty?
      @str.clear
    end
  end
end

def ods2tsv(filename)
  content_xml = Zip::ZipFile.open(filename) do |zf|
    zf.file.read('content.xml')
  end
  doc = Doc.new
  parser = Nokogiri::XML::SAX::Parser.new(doc)
  parser.parse(content_xml)
  doc.tsv
end

require 'tempfile'
a = Tempfile.new('diff_ods_a_')
b = Tempfile.new('diff_ods_b_')
begin
  a.write(ods2tsv(ARGV[0]))
  b.write(ods2tsv(ARGV[1]))
  a.close
  b.close
  system 'C:/Program Files/WinMerge/WinMergeU.exe', a.path, b.path
ensure
  a.close!
  b.close!
end

ODSファイルのテキスト化については以前 xdoc2txtとODSファイル - 単なる日記@はてな という日記を書いたが、今回の差分表示は以前よりはマシな感じになってる(ように見える)。凝りだすときりがないのでこれはここまで。仕事に戻ります。

2013-01-28 17:17

  • 行末に余分なコンマが出力されていたのを修正した。
  • セルの内容によって複数回Doc#charactersが呼び出されても問題ないようにした。
  • 複数回同じセルの内容が続くとうまく出力されなかったのを修正した。