この記事はドワンゴAdventCalendar 24日目の記事です。
12月24日はクリスマスイブですね。クリスマスイブといえば子供のころは「お年玉がもらえる元旦」「誕生日プレゼントが貰える誕生日」、と並んで「一年のうちの三大楽しみな日」だったような気がします。さすがに今ではそこまで楽しみな日というわけではないですが、ちょっとは楽しみな日かもしれません。
という前振りは一切関係なく、以前社内LTでのネタとしてnode-webkitを使ってシンプルなEPUBリーダーを作ったのですが、今回はそのときの話について触れたいとおもいます1。
node-webkit
node-webkitとは、githubのREADME.mdの一行目にもあるように、Chromiumとnode.jsをベースにしたアプリケーションのランタイムです。
今回の記事ではnode-webkit自体の詳細は割愛しますが、OSネイティブなアプリをHTML+JavaScriptで書いて実行できます。Node.jsのAPIやnpmを利用してthird-partyのモジュールも利用可能です。
node-webkitに関しては、githubのWikiが充実しているのでそちらもご参照ください。
後ほど触れますが、EPUBでは実際のコンテンツはXHTMLで記述されているため、「単純にXHTMLを表示させるだけのアプリを作ればリーダーっぽくなるのでは」みたいなことを考えたのがそもそものきっかけです2。
EPUB
EPUBという単語については最近見る機会が増えてきたのかな、と思います。電子書籍の規格の一種で、複数のXHTMLを一つの本としてパッケージングされています。IDPFにより、独自規格ではなく公開された共通規格として世界準標としての規格化が進められています。
EPUBを読む?
EPUBを読むにはEPUBに対応したリーダーが必要となります。AppleのiBooks等、現状でも使いやすそうなEPUB3リーダーが存在します。また、IDPFもReadiumというリーダーの開発を進めています。
前述の通り、EPUBファイルは「複数のXHTMLを一つにパッケージングした」ものです。EPUBファイル自体は「.epub」という拡張子がついているものの、単純なzip圧縮なので、展開した後にEPUBファイル内にある単体のXHTMLをブラウザで読むと普通に表示されます。
EPUBファイルの構造
今回は青空文庫のファイルを有志が開発した、AozoraEpub3を利用してEPUBにしたものを利用しようと思います。
早速EPUB化したファイルを解凍し、EPUBの構造を見ていきます。
.
|-- META-INF
| `-- container.xml
|-- OPS
| |-- css
| | |-- horizontal.css
| | |-- horizontal_font.css
| | |-- horizontal_image.css
| | |-- horizontal_middle.css
| | `-- horizontal_text.css
| |-- package.opf
| |-- toc.ncx
| `-- xhtml
| |-- 0001.xhtml
| |-- 0002.xhtml
| `-- nav.xhtml
`-- mimetype
いくつか見慣れないファイルがあるものの、普通にXHTMLファイルやCSSファイルが含まれていることが分かるかと思います。見慣れないファイルのうち、今回必要となるものについて少し解説します。
META-INF/container.xml
container.xmlにはEPUBを読む上で必要なOPFファイルの位置が記述されています。
具体的には以下のような記述がされています。
<?xml version="1.0" encoding="UTF-8"?>
<container xmlns="urn:oasis:names:tc:opendocument:xmlns:container" version="1.0">
<rootfiles>
<rootfile full-path="OEBPS/what-is-epub3.opf" media-type="application/oebps-package+xml" />
</rootfiles>
</container>
EPUBファイルのルートディレクトリ直下に配置される必要があり、リーダーはまずこれを読むことでOPFファイルの位置を特定し、実際に読み込みます。
OPFファイル
上記の構成内ではpackage.opf
となっているファイルのことです。書籍のメタデータ、パッケージ内に含まれるファイルの一覧、ファイルの読み込み順リストなどが記述されています。
tocファイル
上記の構成内ではtoc.ncx
と呼ばれるファイルです。目次に関する情報が記述されています。
これらのファイルを利用して、実際にシンプルなリーダーを作ります。
EPUBリーダーを作る
EPUBリーダーをつくるにあたり、実際に中身が読めるまでにやらないといけないことを考えます。今回は以下のような処理の流れになります。
- 受け取ったEPUBファイルを展開する
- 展開されたEPUBファイルのMETA-INF/container.xmlを読む
- container.xmlに記述されているOPFファイルを読み、ファイルの読み込み順を決める
- iframeを用意し、そこでページを表示する。
1のファイル展開については普通にzip展開するだけです。2についてはxmlをパースし、rootfile
要素のfull-path
属性を読めばOPFファイルの位置は特定できます。今回は3について詳細を説明します。
ファイルの読み込み順を決める
OPFファイルの中身を読むことでファイルの読み込み順を決定できます。まず、パッケージ内にどのようなファイルが含まれているか、を確認する必要があります。manifest要素を見るとどのようなファイルが含まれるかを確かめることができます。今回の本では以下の様な記述がされています。
<manifest>
<item id="horizontal" href="css/horizontal.css" media-type="text/css"/>
<item id="h_font" href="css/horizontal_font.css" media-type="text/css"/>
<item id="h_text" href="css/horizontal_text.css" media-type="text/css"/>
<item id="h_middle" href="css/horizontal_middle.css" media-type="text/css"/>
<item id="h_image" href="css/horizontal_image.css" media-type="text/css"/>
<item id="nav" properties="nav" href="xhtml/nav.xhtml" media-type="application/xhtml+xml"/>
<item id="sec0001" href="xhtml/0001.xhtml" media-type="application/xhtml+xml"/>
<item id="sec0002" href="xhtml/0002.xhtml" media-type="application/xhtml+xml"/>
<item href="toc.ncx" id="ncx" media-type="application/x-dtbncx+xml"/>
</manifest>
ファイルの読み込み順はOPFファイルのspine
要素に記述されています。今回の本のOPFファイルのspine要素には以下のような記述がされています。
<spine page-progression-direction="ltr" toc="ncx">
<itemref idref="sec0001" linear="yes"/>
<itemref idref="sec0002" linear="yes"/>
</spine>
spine要素の子要素であるitemref要素を順番に見ていけばファイルの表示順を特定できます。itemrefには実際のファイル名の記述はなく、idref
属性があります。これはmanifest
要素のitem
を識別するためのもので、itemref
のidref
と、
item
要素のid
を突き合わせることでどのファイルをどういう順番で表示するか、を決定できます。ファイルの表示順さえ決まっていれば、あとはその順番でXHTMLファイルをiframeで表示させるだけでよさそうです。
つくってみた
実際につくってみたのはこんな感じのものです。あまりにシンプルすぎて雑になっている部分は今後修正が必要ですが、EPUBファイルを読み込んで実際に内容を表示する、という当初の目的については想像していたよりは簡単につくることができました。
まとめ
今回はシンプルなEPUBリーダーを作るという目的を達成するために何をやらねばならないか、ということについて書きました。現時点までのリーダーは、ファイルによってちょっと挙動が怪しくなる部分があり、もう少し修正とブラッシュアップが必要ですが、そのへんがいい感じにできたら公開したいな、みたいな気持ちです。
予定地: https://github.com/tkzwtks/simple-epub-reader
明日はいよいよ最終日です。