この前万能ドキュメント変換器Pandocを紹介した ばかりですが、
もう少し触ってみました。

→Pandoc関連記事

メルマガに書くための資料集めとして、
まとまった情報が書かれているサイトを
wget で保存して org-mode で読もうと奮闘している過程で
この記事を書いています。

HTML文書を org文書 に変換しておくことで、
元々HTMLだった文書をリンクや画像を保持したまま
grep検索 できるようになります。

HTMLのままgrepもできないことはないですが、
HTMLは人間にはとても読みづらいフォーマットです。

Debian testingに含まれている Pandoc 1.12.4.2だと、
org-modeへの変換はバグがあって使いものになりません。

  • 表のx軸とy軸が逆(transposeされている)
  • div id="hogehoge"形式のアンカーが反映されない

Pandoc最新版をインストール

そこで、Haskell版CPANといえる cabal
最新のPandocをインストールしたところ、
嬉しいことにこれらの問題は解決していました。

PandocはHaskellで書かれているので、まずは
Haskell処理系 GHCcabal をインストールしておきます。

そして、cabalでPandocをインストールします。

$ sudo apt-get install ghc cabal
$ cabal update
$ cabal install pandoc

Pandocはたくさんのパッケージに依存しているので初回インストールは
けっこう時間がかかるがじっくり待てば ~/.cabal/bin/pandoc に
インストールされます。

使い方

URLで指定されたページをorgに変換するには
以下のようにします。

前回とは違い --no-wrap オプションを付けておけば、
orgに変換されたときに不自然な改行がなくなります。

Pandocで変換されたorg文書は不自然な改行のために
リンクがおかしくなっていましたが、
その問題が見事に解決されます。

また、UTF-8以外の文書は受け付けないのでnkfで変換しときます。

$ curl -s URL | nkf -w | ~/.cabal/bin/pandoc --no-wrap -f html -o output.org
output.orgに出力

後処理とワイルドカード指定で一括変換

最新のPandocでもいくつか変換結果に問題がありますが、
ラッパースクリプト pandoc-org.rb を書いて解決させます。

  • BEGIN_HTML〜END_HTMLでHTMLタグが出現する→カット
  • 行末に \\ が現れる→カット
  • ファイルへのリンクが正しく変換されない→file: リンクに変換
  • 「foo.html#id」のリンク→foo.org::idに変換
  • JavaScriptへのリンクが現れる→カット
  • 立て続けにリンクがあると org-next-link で行けない→間にスペースを挿入
  • 本文中に_が現れると \_ になってしまう→そのまま_と表示する
  • pandoc -t org foo.htmlしたらfoo.orgではなくて標準出力に出力される→foo.orgに出力させる

pandoc-org.rbはこのようにして使います。

  1. wgetでサイトをhtmlでダウンロードする
  2. pandoc-org.rbですべてのhtmlをorgに変換する
$ wget -r -l1 -p -k http://www.example.com/
$ pandoc-org.rb *.html

pandoc-org.rb(以下のコードと同一)

#!/usr/local/bin/ruby
# -*- coding: utf-8 -*-
require 'parallel'
require 'tempfile'
require 'kconv'

def convert(html, org)
  # utf8しか受け付けないのでnkfで変換する
  tf = Tempfile.new("html")
  s = NKF.nkf("-m0 -w -Lu", File.read(html))
  s.gsub!(%r!</?code.*?>!i, '')                      # <code>
  s.gsub!(%r!</?textarea!i, 'pre')
  tf.write s
  tf.close
  system "~/.cabal/bin/pandoc --no-wrap -f html #{tf.path} -o #{org.inspect}"

  s = File.read(org)
  open(org, "w") do |f|
    f.write process_org(s)
  end
end

def process_org(s)
  s = s.clone
  s.gsub!(/#\+BEGIN_HTML.+?#\+END_HTML\n\n/m, '') # うざいのでカット
  s.gsub!(/[\\][\\]$/, '')                        # 行末の\\
  s.gsub!(/\\_/, '_')                             # \_
  s.gsub!(/\[\[.+?\]\[\]\]\n?/, '') # [[URL][]]
  s.gsub!(/<<>>\n?/, '')            # <<>>

  # file:リンクへ正しく変換されないのはバグよね
  s.gsub!(/\[\[javascript:[^\[]+\]\[.+?\]+/, '') # javascript link
  s.gsub!(/\[\[([^\[]+?)\]/) do
    link = $1
    orig = $&
    file, id = $1.split(/#/)
    if not file
      newlink = nil
    elsif file !~ /^http/
      newlink = "file:" << file.sub(/html?$/, 'org')
      if id
        newlink << "::#{id}"
      end
    else
      newlink = link
    end
    newlink ? "[[#{newlink}]" : orig
  end
  s.gsub!(/\]\]\[\[/, ']] [[')  # 隣合うリンクの間にスペースを入れる
  s
end

Parallel.each(ARGV) do |html|
  convert html, html.sub(/html?$/, 'org')
end

本日もお読みいただき、ありがとうございました。参考になれば嬉しいです。