LoginSignup
0
0

data: URL 向けに文字列を%エンコーディングするRubyワンライナー

Last updated at Posted at 2023-06-07

CSS に data: URL を使って、データを埋め込む場合、画像などは Base64 エンコーディングを利用しますが、埋め込みたいデータが SVG などのテキストをベースとするデータの場合、 Base64 エンコードではなく、URIの標準的なエスケープ方法(%エンコーディング) を使う方が可読性やサイズなどの点で有利になる場合があります。

この際に重要なのは、埋め込む際にどの文字をエスケープしなければならないかの情報です。
この記事では、 Fetch Standard の要件の簡単なまとめと、それに従ったエスケープを行うための Ruby ワンライナーを記載します。

とりあえず結論を見たい方は エスケープを行うワンライナー を御覧ください。

エスケープが必要な文字

RFC2397 (Obsoleted)

"data" URL scheme を提案した RFC2397 で定義されているデータ本体(body)に用利できる文字は、RFC2396 で定義されているURIに利用できる文字(uric)に準拠しており、エンコードしなければならない文字の判定は大体以下の Ruby コードで表すことが出来ました:

RFC2397準拠
require "erb"
alphanum = %q{[a-zA-Z0-9]}
mark = %q{[-_.!~*'()]}
reserved = %q{[;/?:@&=+$,]}
char2escape = /[^#{reserved}#{alphanum}#{mark}]/
ans = ''
while gets
  print($_.chomp.gsub(char2escape){|c|ERB::Util.url_encode(c)})
end

要するに、この仕様では 文字クラス(正規表現における文字の集合) alphanummarkreserved のいずれにも含まれていない文字はエスケープ対象です。
※こちらの仕様では結構多くの文字がエスケープされます。

Fetch Standard

しかし、近年のブラウザ標準を定めている WHATWGFetch Standard ではこの仕様を大幅に緩和していて、簡単にいうと以下のような仕様になります。

  1. data: URL の body 部分は byte sequence で構成される。
  2. byte sequence は 0xXX 形式の byte をスペースで区切ったものとして表記される。
  3. ただし、byte の値の範囲が 0x200x7e に収まっている byte sequence は通常の文字列として表示しても良い。※body はこの規定に基づいて文字列で表現されている。
  4. body を解釈する前には%デコードを行う。すなわち、エスケープするべき文字は data: URL では%エンコードされる。
  5. # (0x23) は URI における Fragment の予約文字として解釈されるので、この値が body 中で出てくる場合はエスケープする

また、この data: URL を CSS や HTML に埋め込む場合、以下のような記法になります:

CSS
:root {
  --ver: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'>...</svg>");
}
HTML
<img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'>...</svg>"

この際、URLの中身はダブルクォーテーション " により囲んで記載する関係で、CSSに利用する場合はこの文字もエスケープしないとブラウザのパーサが理解することが出来なくなりますので、次の副次的な要件が発生します:

  1. " (0x22) もエスケープする

エスケープを行うワンライナー

上記を反映した、SVGなどのテキストデータを data: URL の body に埋め込むためにエスケープするための Ruby ワンライナーは以下になります:

Fetch Standard準拠
ruby -r cgi -npe '$_.chomp!.gsub!(/[^ !\x24-\x7e]/){|c|CGI.escape(c)}' < $INPUT

※改行コードを残す場合は chomp! を削除して $_.gsub! としてください。

補足: [\x20-\x21] は2文字だけなので下記のように実際の文字で表記しています:

正規表現 文字 補足
\x20 半角スペース
\x21 ! エクスクラメーションマーク

多くの場合動く代替案

Chrome などの実際のブラウザの多くでは body 部分の文字列が [\x20-\x7e] の範囲に収まっていなくても UTF-8 の範囲であればきちんと解釈してくれるように見えますので、標準に厳密に準拠する必要がないのであれば、以下の変換でもおそらく十分です。

省略版
ruby -r cgi -npe '$_.chomp!.gsub!(/[#"]/){|c|CGI.escape(c)}' < $INPUT

SVGやXMLなどを埋め込む際のコツ

XMLや(XMLに基づく)SVGでは、Attribute Value を囲うための記号として、ダブルクォーテーション " だけでなくシングルクォーテーション ' も使えるので、できるだけシングルクォーテーションを使うようにすると、エスケープの量を最小化することが出来ます。

例1.svg ※こちらだとエスケープが増える
<svg xmlns="http://www.w3.org/2000/svg" style="background:#AAA"></svg>
エスケープ後
<svg xmlns=%22http://www.w3.org/2000/svg%22 style=%22background:%23AAA%22></svg>

例1.svg と同じ内容をシングルクォーテーションで書いたものはこちら:

例2.svg ※こちらがベター
<svg xmlns='http://www.w3.org/2000/svg' style='background:#AAA'></svg>
エスケープ後
<svg xmlns='http://www.w3.org/2000/svg' style='background:%23AAA'></svg>

こちらだとエスケープは # 一箇所です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0