NS アウトライナーのファイルから平文を取り出す

既に公開されなくなって随分経ってしまった Acta 風のアウトライン・プロセッサ、Windows 用の NS アウトライナーのファイルから平文テキストを取り出せないものかと 16進ダンプを眺めていまして、なんとかアウトラインのレベルと平文だけは取り出せるようになりました。
ファイルの構造は次の通りです。レベルの入れ子の情報はファイルには入っていないようで、レベルだけが数値で記録されているだけのようです。例えるなら、はてな記法の + や - みたいな感じで、トピックが格納されていました。

  1. ファイルヘッダ 1200 バイト固定サイズ。平文を取り出すだけならスキップすればいいみたいです。
  2. トピックの並び。以下、ヘッダとテキストが交互に並んでいます。
    1. トピック・ヘッダ 144 バイト固定サイズ。
      • 72 バイト目からリトル・エンディアン32ビット整数でレベルが記録されています。
      • 76 バイト目から同上でトピックのテキストサイズが記録されています。
    2. テキスト。トピック・ヘッダのテキストサイズ分が可変長で格納されています。

他にもトピックごとの文字フォーマット、色情報、閉じているかどうかの情報が入っているはずですが、私の目的は平文をレベル込みで取り出すことですから、わからなくてもいいやということで追求するのは止めました。
ということで、NS アウトライナーのエクスポートと同じように、平文を取り出す Ruby スクリプトを作ってみました。MARKER を変えたり、出力部を変更することで、NS アウトライナーのエクスポート機能の不満が解消するといいなぁ。

#!/usr/local/bin/ruby

# NS outliner exporter
# usage: ruby nsoexport.rb nso_file_name > plaintext

module NSO
  def self.foreach(file_name)
    File.open(file_name, 'rb') do |f|
      f.seek 1200, IO::SEEK_SET # first top level
      while header = f.read(144)
        level, text_size = header.unpack('x72VV')
        text = f.read(text_size).gsub(/\r/, '')
        yield level, text
      end
    end
  end
end

MARKER = '*'

file_name = ARGV.shift or raise "file name?"
NSO.foreach(file_name) {|level, text|
  text.each {|line| puts MARKER * level + text }
}