Nwht0xn1

HTML内のCSS定義をstyle属性に置き換えるやつをつくったCreated on 2016-05-22 by r7kamura

StyleInlinerという、HTML内のCSS定義をstyle属性に置き換えるやつをつくった。

ソースコード

StyleInlinerは、例えば以下のようなHTMLを読み込んで、

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <style>
      body {
        background-color: red;
      }
    </style>
  </head>
  <body>
  </body>
</html>

以下のようなHTMLに変換する。

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  </head>
  <body style="background-color: red">
  </body>
</html>

経緯

週末にCSSとHTML、そしてHTMLメールの実装の勉強をしようと思って、練習に書いてみた。実際、CSSの用語の定義や、詳細度の計算手順、!important 定義に関する優先順位のルールに加え、HTMLメールを組み立てるにあたって考えなければならない色々なことについて学べた (まあHTMLメールについては、現実的にはHTMLを組み立てることよりも配信に関する事柄のほうが問題になることが多いのだけど)。

似たような処理を実現するRuby用のライブラリとして、Premailer という既存の実装がある。こちらはHTMLメールのための前処理を行うためのライブラリとして最適化されている。他にも https://www.ruby-toolbox.com/categories/Inline_CSS_for_E-Mail を見ると、同様のライブラリが多く存在することが分かる。今回は主にPremailerを参考にさせてもらいながら、処理の追いやすさを重視してコードを組み立てていった。

基本原理

基本的には、読み込んだHTMLから関係するCSSの規則セットを全て読み込み、定義されたCSSセレクタを使ってHTML中のそれぞれの要素にCSSの定義をあてはめ、style属性を付けていく。ソースコードで言うと、以下の部分がその処理にあたる。

module StyleInliner
  class Document
    # @param html [String]
    # @param replace_properties_to_attributes [false, true]
    def initialize(html, replace_properties_to_attributes: true)
      @html = html
      @replace_properties_to_attributes = replace_properties_to_attributes
    end

    # @return [Nokogiri::XML::Node]
    def inline
      load_styles_from_html
      merge_styles_into_each_element
      fold_style_attributes
      append_style_element_for_unmergeable_rule_sets
      root
    end
  end
end

#inlineStyleInliner::Document.new(html).inline のように呼び出して使う。それぞれのメソッドの役割は以下の通り。

  • #load_styles_from_html - CSSの規則セットをHTMLから読み込む
  • #merge_styles_into_each_element - それぞれの要素にルールを割り当てる
  • #fold_style_attributes - 要素ごとにルールを畳み込んでstyle属性の値を決定する
  • #append_style_element_for_unmergeable_rule_sets - style属性で表現できないものに対応する
  • #root - 変換後のHTMLツリーの根要素を返す

詳細度

CSSには適用するときの優先順位を計算するための詳細度という値があるので、これを考慮することに注意する。

擬似クラス

a:hover のような擬似クラスを利用するセレクタは、style属性では表現できない。こういったセレクタを利用する定義が存在する場合、それらはまとめて1つのstyle要素として追加する。HTMLメールでは、幾つかのメーラーではstyle要素内で定義されたCSSを読み込まない場合がある (例: Gmail) ので、仮に適用されなくても見られるようなデザインにする必要がある。HTMLメールを組み立てるときに考慮すべき事柄については HTMLメールを作成するために拾い読んだ情報源 - Programming にリンクをまとめてあるので、興味があれば読んでみるとよいかもしれない。

CSS properyからHTML attributeへの変換

CSSのbackground-colorプロパティとtable要素におけるbgcolor属性などのように、幾つかの要素ではCSSプロパティと互換性のある属性を持つものがある。上述したHTMLメールでの事情を考え、属性に置き換えられる場合には自動的に置き換えるような実装を施してみた。

展望

少しだけ実装してみたところ、動作原理についてはある程度把握できた。Premailerはそこまで活発に開発が進んでいないようなので、仕事や趣味のプロジェクトの要件で必要になれば、StyleInlinerの機能を拡充しながらこれを利用していくかもしれない。WikiHubで自前でHTMLメールを送る機会が出てきた場合は、ActionMailerと組み合わせて適当にスタイルを管理する目的で使うかも。直接利用することはなくても、例えばMarkdownのようなユーザがある程度自由にHTMLの要素を記述できるコンテンツを含むページをAMP対応したいとか、そういう場合に今回の実装に利用した設計が使えるような気がしている。