Nokogiri で encoding error

以下のファイルを Nokogiri で解析させるとエラー。

  • demo-1.html
<?xml version="1.0" encoding="euc-jp"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang="ja" xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja">
<head>
<meta name="keywords" content="ロ" />
<meta http-equiv="Content-Type" content="text/html; charset=EUC-JP" />
</head>
<body>
テスト
</body>
</html>
  • ex-1.rb
require 'rubygems'
require 'nokogiri'
require 'open-uri'

doc = Nokogiri(open(ARGV[0]))
puts doc.to_html

実行

$ ruby ex-1.rb demo-1.html |iconv -f euc-jp -t utf-8
encoding error : output conversion failed due to conv error, bytes 0x95 0x24 0xC8 0x26
I/O error : encoder error

http://d.hatena.ne.jp/kitamomonga/20100712/ruby_mechanize_loses_to_euc_html_tips
を読むと、meta charset の前にマルチバイト文字(特定の文字。上の例では
meta keywords に カタカナの"ロ")が 書かれている場合に起きる。

demo-1.html を、charset の後にマルチバイト文字が来るように書き換えると

  • demo-2.html(抜粋)
<head>
<meta http-equiv="Content-Type" content="text/html; charset=EUC-JP" />
<meta name="keywords" content="ロ" />
</head>

実行

$ ruby ex-1.rb demo-2.html |iconv -f euc-jp -t utf-8
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<?xml version="1.0" encoding="euc-jp"?><html lang="ja" xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=EUC-JP">
<meta name="keywords" content="">
</head>
<body>
テスト
</body>
</html>

無事 出力される。

とは言え、世に公開されている膨大な HTML を手動で書き換えるわけには
いかないので、プログラムで対処する。

  • ex-2.rb
require 'rubygems'
require 'nokogiri'
require 'open-uri'

require 'kconv'

str = open(ARGV[0]).read

noko_en_id = {
  Kconv::UTF8 => 'UTF-8',
  Kconv::EUC => 'EUC-JP',
  Kconv::SJIS => 'SHIFT-JIS',
  Kconv::ASCII => 'ASCII',
  Kconv::JIS => 'ISO-2022-JP',
#   Kconv::UTF16 => ,
#   Kconv::UNKNOWN => ,
}[Kconv.guess(str)] || raise

doc = Nokogiri::HTML.parse(str, nil, noko_en_id)
puts doc.to_html

実行

$ ruby ex-2.rb demo-1.html |iconv -f euc-jp -t utf-8
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<?xml version="1.0" encoding="euc-jp"?><html lang="ja" xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja">
<head>
<meta name="keywords" content="">
<meta http-equiv="Content-Type" content="text/html; charset=EUC-JP">
</head>
<body>
テスト
</body>
</html>

無事出力される。二度手間感は否めないが