SVGのimage要素での外部画像ファイル参照をbase64コード（URIスキーマ）に変換する

この記事は、[SVG Advent Calendar 2016](http://www.adventar.org/calendars/1428)の6日目の記事です。
また、[説明つきの画面キャプチャー図をSVGでつくる](http://qiita.com/j8takagi/items/5d5f5de5c0d8b1bb788e)の続編です。

## はじめに
[説明つきの画面キャプチャー図をSVGでつくる](http://qiita.com/j8takagi/items/5d5f5de5c0d8b1bb788e)で取り上げたようにSVGのimage要素を使って外部の画像ファイルを参照させると、いろいろ便利です。
しかし、外部の画像ファイルを参照させたSVGファイルをHTMLのimgタグなどで参照しようとすると、うまくいきません。画像ファイルが欠落してしまうのです。
とりあえずの対策として、SVGファイルをPNGなどの画像に変換するという方法もあります。
だけど、せっかく世の中の大多数のWebブラウザーでSVGを扱えるようになった時代、できればSVGのままHTMLから使いたいものです。

## 外部画像ファイル参照をbase64コード（URIスキーマ）に変換する
いろいろ調べると、image要素で参照する画像ファイルをbase64コードに変換してURIスキーマを使えば、HTMLのimgタグなどで参照しても画像ファイルが欠落しないらしいです。

画像ファイルをbase64コードに変換するというのも、調べればいろいろな方法がでてきます。
[Ruby](https://www.ruby-lang.org/ja/)を使う場合は、次のプログラムで画像ファイルをbase64コードに変換できます。

次のRubyプログラムファイルを作成します。
なお、[library base64](https://docs.ruby-lang.org/ja/latest/library/base64.html)はRubyの標準添付ライブラリです。

```ruby:imgfile2base64.rb
require 'base64'

def imgfile2base64(imgfile)
  Base64.strict_encode64(File.new(imgfile).read)
end

puts imgfile2base64(ARGV[0])
```

シェルなどから次のように実行することで、base64コードを出力できます。

```
$ ruby imgfile2base64.rb 01.png
```

次にSVGファイルで、```image```要素の```xlink:href```属性の内容を置換します。
```xlink:href```属性として記載されているファイル名を削除し、かわりに```data:image/png;base64,```を入力します。
```image/png```の部分は、参照する画像ファイルの [MIMEタイプ](http://www.tohoho-web.com/wwwxx015.htm)。
```data:image/png;base64,```に続き、base64コードをコピーアンドペーストするなどして入力し、保存します（別名での保存を推奨）。

ここまで作業したら、SVGファイルが正常に表示されるかテストしてください。
まずは置換後、もとのファイルと同じようにSVGファイルが表示されていることを確認してください。
確認できたら次に、このSVGファイルをHTMLファイルからimg要素を使って参照してみましょう。
HTML文書中で画像部分も含めてSVGが正常に表示されていれば成功です。

## base64コードへの変換を自動化する

image要素のxlink:href属性に記載されているファイル名をbase64コードに置換する作業は、自動化したいですね。

### ライブラリのインストール
Rubyで、XMLドキュメントの要素・属性値を取得・変更するときには、[nokogiri](http://www.nokogiri.org)ライブラリを使います。
また、MIMEタイプを取得するためには、[mime-types](https://github.com/mime-types/ruby-mime-types/)ライブラリを使います。
ということで、この2つのライブラリをインストールしておいてください。

必要なライブラリがインストールされているか確認。インストールされていない場合は、何も表示されません。

```
$ gem list | grep '\(nokogiri\)\|\(mime-types\)'
```

nokogiriライブラリをインストール。

```
$ sudo gem install nokogiri
```

mime-typesライブラリをインストール。

```
$ sudo gem install mime-types
```

必要なライブラリがインストールされているか確認。インストールされている場合は、次のように表示されます。

```
$ gem list | grep '\(nokogiri\)\|\(mime-types\)'
mime-types (3.1)
mime-types-data (3.2016.0521)
nokogiri (1.6.6.2)
```

### svg-image-base64.rb

nokogiriとmime-typesライブラリを使えば、base64コードへの置換自動化が実現できます。

```svg-image-base64.rb
require 'nokogiri'
require 'base64'
require 'mime/types'

def imgfile2base64(imgfile)
  Base64.strict_encode64(File.new(imgfile).read)
end

def image_mimetype(filename)
  MIME::Types.type_for(filename).each do |mime|
    return mime if mime.media_type == "image"
  end
  nil
end


docpath = ARGV[0]
if not File.exist?(docpath)
  STDERR.printf "Error: file %s is not found.", docpath
  exit 1
end
doc = Nokogiri::XML(File.open(docpath))
Dir.chdir(File.dirname(docpath)) do
  doc.css('image').each do |node|
    imgfile = node.attributes["href"].value
    if not File.exist?(imgfile)
      STDERR.printf "Warn: image file %s in %s is not exist.", imgfile, docpath
    else
      mimetype = image_mimetype(imgfile)
      if mimetype.nil?
        STDERR.printf "Warn: %s is not image file.", imgfile
      else
        node.attributes["href"].value = 'data:' << mimetype << ';base64,' << imgfile2base64(imgfile)
      end
    end
  end
end
print doc.to_s
```

svg-image-base64.rbの保存後、シェルなどから1番目の引数にSVGファイル（ここでは、a.svg）を指定し、次のように実行します。
SVGファイルの```image```要素の```xlink:href```属性に記載されているファイル名をbase64コードに置換した結果が標準出力に出力されます。
ここではシェルのリダイレクト機能を使い、ab.svgファイルを作成しています。

```
ruby svg-image-base64.rb a.svg >ab.svg
```

実行後、ab.svgを直接開いてa.svgと同じ図が表示されること、HTMLファイルから```img```要素を使って参照したときに正常に表示されることを確認してください。

## まとめ

SVGファイルで、```image```要素を使って外部の画像ファイルを参照すると、問題が発生する場合があります。
その対策として、画像ファイルをbase64コードに変換したデータに置換します。こうした置換は、自動化できます。