それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

Opera用にMhtファイルを保存する。

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ページ全体のファイルを
アーカイブして電子メールに添付して送信する
ために考えられたものであり、MIMEの定義を
拡張した形でRFC 2557で規格化されている。

http://it-words.cybozu.net/content/MHTML

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が「保存に失敗しました」と容赦なくメッセージ。実際にはまるで使えない。

『このページは保存できませんでした』への対応法

保存できなかったページがあったとき、IEの設定で「アクティブ スクリプト」を「無効にする」とそのページを保存できるようになります。

具体的な設定の仕方は以下のとおりです。

  • IEのメニューで、ツール>インターネットオプション>"セキュリティ"タブ>"レベルのカスタマイズ"ボタン>とすると「セキュリティ設定」画面が表示されるので、

その画面の「スクリプト」項目の小項目「アクティブ スクリプト」を「無効にする」を選択状態にします。

  • そして「OK」を2回してインターネットオプション画面から抜ける。保存したいページを表示してる状態で設定を変更したならページのリロードが必要です。
  • この設定変更により保存できなかったページが、必ずしも全て上手く行くわけではありませんが保存できるようになる場合が多いです。設定で無効にしっぱなしにすると不便なこともあるので好みで適宜設定するのもいいでしょう。
このページは保存できませんでした/Webページの保存エラー - まりふのひと

とのこと、確かに、hatenaのトップページはこれで保存できるようになった。

でもRubyから作ったファイルはまだIEで開かない。困った。