HTMLで作成したメモを人に送りたいなと思った。イチイチZipするのも面倒だし。Mht形式で送れば楽だ。そこでMHTを生成するRubyファイルを書いてみた。MhtはOpera生成ファイルを参考にした。
Mht作れるが、Operaでしか開けない。あぁ
Opera互換のMhtファイルを作成する。
#!/usr/bin/env ruby #正規表現だとアレコレ面倒なので、Nokogiriする。 require 'rubygems' require 'nokogiri' require 'open-uri' require 'base64' require 'digest/md5' require 'stringio' module OperaMhtml class Partial def initialize(url) @uri = URI(url.to_s) end def to_mime @file = @uri.read str = self.header str = str + "\n" str = str + self.body str = str.gsub("\r", "") str = str + "\n" end def to_s self.to_mime end def body str = "" str = @file str = Base64.encode64(@file.to_s) #unless @file.content_type =~ /^text.+/ str end def header #encoding = "7bit" #encoding = "8bit" if @file.charset =~ /^utf.+/ encoding = "base64" #if @file.content_type =~ /^image.+/ str=""+ "Content-Disposition: inline; filename=#{File::basename(@uri.path)}\n" + "Content-Type: #{@file.meta['content-type']};\n" + "Content-Location: #{@uri.to_s}\n" + "Content-Transfer-Encoding: #{encoding}\n" end end class Archiver def initialize(uri) @uri = URI(uri.to_s) @page = @uri.read @doc = Nokogiri( open(uri).read) @boundary = "____"+Digest::MD5.hexdigest(@page) end def base_uri @uri.to_s end def to_s self.to_mhtml end def to_mhtml io = StringIO.new io.write self.header io.write "\n" self.src_links.each{|e| begin tmp=StringIO.new tmp.write("--"+@boundary +"\n") tmp.write Partial.new(e).to_s tmp.rewind io.write tmp.read rescue #puts e end } io.rewind io.read end def subject t= @doc.search("title").text return "=?utf-8?B?#{Base64.encode64(t).chomp}?=" end def src_links external_src_uri_list = [] img_src_list = [] @doc.search("//img").each do|e| img_src_list.push URI.join(self.base_uri, e.attr("src")) if e.attr("src") end script_src_list = [] @doc.search("//script").each do|e| script_src_list.push URI.join(self.base_uri, e.attr("src")) if e.attr("src") end css_src_list = [] @doc.search("//link[@rel='stylesheet']").each do|e| css_src_list.push URI.join(self.base_uri, e.attr("href")) if e.attr("href") end ## このほかにも、@import / image src をbase64で表現するなどある。 ## 今回は考慮無し。 css_internal_url_list = css_src_list.map{|e| e.read =~ /url\(([^\)]+)\)/ URI.join(base_uri, $1) } external_src_uri_list = img_src_list + script_src_list + css_src_list + css_internal_url_list @external_src_uri_list = external_src_uri_list.uniq @external_src_uri_list.unshift @uri end def header str="Content-Type: multipart/related; "+ "start=<op.mhtml.#{Time.now.to_i}.#{@boundary}@#{@uri}>; "+ "boundary=#{@boundary}\n"+ "Content-Location: #{@uri}\n"+ "Subject: #{self.subject()}\n"+ "MIME-Version: 1.0\n" end end end
使用サンプル
open("sample1.mhtml", "w"){|f| mhtml =OperaMhtml::Archiver.new("http://www.hatena.ne.jp/") f.write mhtml.to_s }
Operaが作るMhtの特徴について
Content-Transfer-Encoding について
Content-Transfer-Encoding: Base64 Content-Transfer-Encoding: 8bit Content-Transfer-Encoding: 7bit
とContent-Transfer-Encodingが幾つか形式があった。Base64は当たり前すぎるのでスルーするとして、7bit/8bitについて少し調べてた。
参考
[8bitメールは問題ないのか?[
Content-Transfer-Encoding問題 ‐ 通信用語の基礎知識
メールの場合、8bit 非対応MTA経由すると、8bit情報が欠落する。
しかしUTF-8をそのまま送信するには8bitまま送ればいい。最近は殆どが対応している。
昔はMSが独自で実装したが、これはMSが先走ったと言うものではなくMSが必要だから仕方なくつけて業界をリードしようとしたって事なんだろか。
しかし、IE8が作成するMHTMLには8bitが含まれない。
IEが作るMHTMLのヘッダの特徴
Content-Type: text/html; charset="utf-8" Content-Transfer-Encoding: quoted-printable Content-Location: http://twitter.com/takuya_1st
Operaが作るMHTMLのヘッダの特徴
Content-Disposition: inline; filename=iframe.htm Content-Type: text/html; charset=utf-8; name=iframe.htm Content-Location: http://m.hatena.ne.jp/iframe?limit=5 Content-Transfer-Encoding: 8bit
mhtmlについて
MHTMLについて。規格など
MHTMLは、元来、Webページ全体のファイルを
http://it-words.cybozu.net/content/MHTML
アーカイブして電子メールに添付して送信する
ために考えられたものであり、MIMEの定義を
拡張した形でRFC 2557で規格化されている。
Subjectの扱いについて
基本的にメールのそれと同じ。
メールSubjectのエンコードについて
メールのヘッダ部分はメールがサーバー間で転送されていく段階ごとに再構築されます。その際、ASCII しか使えないので、メールヘッダには日本語をはじめとする非ASCII文字を使用できなくなってしまいます。そこで、RFC1522 において、メールヘッダに非ASCII文字を MIME 処理を施して埋め込む方法が規定されています。たとえば、「日本語のタイトル」という文字列を件名フィールドに埋め込むには、その文字列を次のようにMIME処理します。
=?UTF-8?B?5pel5pys6Kqe44Gu44K/44Kk44OI44OrCg==?=記号のルールは =?<文字コード>?<エンコード形式>?<エンコードされた文字列>?=http://ja.mostlyunix.com/?q=node/8
しかしOperaが作ったファイルは次のようになっている。
Subject: =?utf-8?Q?=E3=81=AF=E3=81=A6=E3=81=AA?=
これは次と同じ
Subject: =?utf-8?B?44Gv44Gm44Gq?=
記号のルールは =?<文字コード>?<エンコード形式>?<エンコードされた文字列>?=
OperaはメールヘッダのSubject形式のB?(base64)も解釈できる。
エンコード形式の『Q』ってなんだ?これは少し分からない。。
IEのMhtmlはCDOMessageを使ってるんだけど、保存できないページがある。
IEが「保存に失敗しました」と容赦なくメッセージ。実際にはまるで使えない。
『このページは保存できませんでした』への対応法
このページは保存できませんでした/Webページの保存エラー - まりふのひと保存できなかったページがあったとき、IEの設定で「アクティブ スクリプト」を「無効にする」とそのページを保存できるようになります。
具体的な設定の仕方は以下のとおりです。
- IEのメニューで、ツール>インターネットオプション>"セキュリティ"タブ>"レベルのカスタマイズ"ボタン>とすると「セキュリティ設定」画面が表示されるので、
その画面の「スクリプト」項目の小項目「アクティブ スクリプト」を「無効にする」を選択状態にします。
- そして「OK」を2回してインターネットオプション画面から抜ける。保存したいページを表示してる状態で設定を変更したならページのリロードが必要です。
- この設定変更により保存できなかったページが、必ずしも全て上手く行くわけではありませんが保存できるようになる場合が多いです。設定で無効にしっぱなしにすると不便なこともあるので好みで適宜設定するのもいいでしょう。
とのこと、確かに、hatenaのトップページはこれで保存できるようになった。
でもRubyから作ったファイルはまだIEで開かない。困った。