这是“文件格式探究”专题的第 1 期——初探 “ePub” 文件格式。这个专题将会给各位读者呈现笔者探索各种文件格式的过程,具体则是文件的内容是 ,后续所有的探究活动均建立于此文件上。笔者目前的操作系统环境为 Manjaro 21.1.0 on amd64,终端环境为 GNU bash 5.1.8(1)-release 。为了方便,我们先把文件改个名字 (那你还把原来的名字给出来干嘛?!) :
[littleye233@lymjrolt Downloads]$ cd ~
[littleye233@lymjrolt ~]$ cd Downloads
[littleye233@lymjrolt Downloads]$ mv 咖啡馆推理事件簿系列(全四本).epub test.epub
[littleye233@lymjrolt Downloads]$ ll test.epub
-rw-r--r-- 1 littleye233 littleye233 1253964 Aug 22 23:24 test.epub
Round I. 文件类型
首先我们先尝试用 Linux 系统的内置命令 file
试试水,看看会输出什么东西。键入 file test.epub
后执行:
[littleye233@lymjrolt Downloads]$ file test.epub
test.epub: EPUB document EPUB document
哎呀,真可惜! file
命令几乎什么有效信息都没给我们。 file
命令的 man
页面明确给出此命令可以判断文件格式,但其实它能做到的有很多,例如如果对一个图片文件使用 file
,可能会出现类似下面的结果:
[littleye233@lymjrolt Downloads]$ file ~/.local/share/osu/screenshots/osu_2021-08-21_23-40-03.png
/home/littleye233/.local/share/osu/screenshots/osu_2021-08-21_23-40-03.png: PNG image data, 1920 x 961, 8-bit/color RGBA, non-interlaced
这样我们可以通过 file
中提供的相关信息顺藤摸瓜,尝试在文件的二进制编码内容中寻找其蛛丝马迹,进而推测对应“位点”所表达的含义 (因为一些文件格式要求在特定的位置表达某些含义) ,如果能提供类似注释的信息就再好不过了。
Round II. 文件结构
现在我们回到这个 ePub 文件上来。现在我们尝试能否直接获取其内容,目的是通过文件头部的部分可见字符猜测其文件结构。输入 nano test.epub
直接预览,或使用 head --bytes=120 test.epub
查看前面 120 个字节的内容:
[littleye233@lymjrolt Downloads]$ head --bytes=120 test.epub
PK!oa�mimetypeapplication/epub+zipPU�N�;�ʯ�META-INF/container.xml]�A
�0E�=
果不其然,我们看到了一些有趣的字眼: “mimetypeapplication/epub+zip” ,凭经验猜测,这应该是 ePub 文件格式的文件头,而其中的 “zip” 也说明—— ePub 文件可能本质上就是一个压缩档。
其实很多文件格式 (例如 Word 文档 “*.docx”) 其本质都是在一个压缩档中加入各种资源文件和配置文件,只要有对应的软件进行读取并重新加工,用户即能看到效果。
Round III. 目录树结构
现在我们可以使用解压缩程序解出 ePub 文件中的内容了。在终端中执行 unzip -l test.epub
:
[littleye233@lymjrolt Downloads]$ unzip -l test.epub
Archive: test.epub
Length Date Time Name
--------- ---------- ----- ----
20 1980-01-01 00:00 mimetype
251 2019-06-27 10:40 META-INF/container.xml
12307 2019-06-27 10:40 OEBPS/content.opf
112368 2019-06-27 10:40 OEBPS/Images/cover00464.jpeg
128680 2019-06-27 10:40 OEBPS/Images/image00456.jpeg
120936 2019-06-27 10:40 OEBPS/Images/image00457.jpeg
1392 2019-06-27 10:40 OEBPS/Images/image00458.jpeg
101948 2019-06-27 10:40 OEBPS/Images/image00459.jpeg
119124 2019-06-27 10:40 OEBPS/Images/image00460.jpeg
1268 2019-06-27 10:40 OEBPS/Images/image00461.jpeg
42944 2019-06-27 10:40 OEBPS/Images/image00462.jpeg
121284 2019-06-27 10:40 OEBPS/Images/image00463.jpeg
2251 2019-06-27 10:40 OEBPS/Styles/style0001.css
9816 2019-06-27 10:40 OEBPS/Styles/style0002.css
2251 2019-06-27 10:40 OEBPS/Styles/style0003.css
9789 2019-06-27 10:40 OEBPS/Styles/style0004.css
2251 2019-06-27 10:40 OEBPS/Styles/style0005.css
29245 2019-06-27 10:40 OEBPS/Styles/style0006.css
2235 2019-06-27 10:40 OEBPS/Styles/style0007.css
29914 2019-06-27 10:40 OEBPS/Styles/style0008.css
2251 2019-06-27 10:40 OEBPS/Styles/style0009.css
624 2019-06-27 10:40 OEBPS/Text/cover_page.xhtml
851 2019-06-27 10:40 OEBPS/Text/part0000.xhtml
561 2019-06-27 10:40 OEBPS/Text/part0001.xhtml
428 2019-06-27 10:40 OEBPS/Text/part0002.xhtml
1518 2019-06-27 10:40 OEBPS/Text/part0003.xhtml
661 2019-06-27 10:40 OEBPS/Text/part0004.xhtml
2311 2019-06-27 10:40 OEBPS/Text/part0005.xhtml
55157 2019-06-27 10:40 OEBPS/Text/part0006.xhtml
58266 2019-06-27 10:40 OEBPS/Text/part0007.xhtml
59953 2019-06-27 10:40 OEBPS/Text/part0008.xhtml
49789 2019-06-27 10:40 OEBPS/Text/part0009.xhtml
66870 2019-06-27 10:40 OEBPS/Text/part0010.xhtml
57342 2019-06-27 10:40 OEBPS/Text/part0011.xhtml
67449 2019-06-27 10:40 OEBPS/Text/part0012.xhtml
16183 2019-06-27 10:40 OEBPS/Text/part0013.xhtml
561 2019-06-27 10:40 OEBPS/Text/part0014.xhtml
428 2019-06-27 10:40 OEBPS/Text/part0015.xhtml
1575 2019-06-27 10:40 OEBPS/Text/part0016.xhtml
496 2019-06-27 10:40 OEBPS/Text/part0017.xhtml
1446 2019-06-27 10:40 OEBPS/Text/part0018.xhtml
52358 2019-06-27 10:40 OEBPS/Text/part0019.xhtml
75746 2019-06-27 10:40 OEBPS/Text/part0020.xhtml
63420 2019-06-27 10:40 OEBPS/Text/part0021.xhtml
57399 2019-06-27 10:40 OEBPS/Text/part0022.xhtml
58590 2019-06-27 10:40 OEBPS/Text/part0023.xhtml
40263 2019-06-27 10:40 OEBPS/Text/part0024.xhtml
66099 2019-06-27 10:40 OEBPS/Text/part0025.xhtml
15143 2019-06-27 10:40 OEBPS/Text/part0026.xhtml
561 2019-06-27 10:40 OEBPS/Text/part0027.xhtml
612 2019-06-27 10:40 OEBPS/Text/part0028.xhtml
1344 2019-06-27 10:40 OEBPS/Text/part0029.xhtml
640 2019-06-27 10:40 OEBPS/Text/part0030.xhtml
6144 2019-06-27 10:40 OEBPS/Text/part0031.xhtml
25197 2019-06-27 10:40 OEBPS/Text/part0032.xhtml
54594 2019-06-27 10:40 OEBPS/Text/part0033.xhtml
87394 2019-06-27 10:40 OEBPS/Text/part0034.xhtml
97557 2019-06-27 10:40 OEBPS/Text/part0035.xhtml
109901 2019-06-27 10:40 OEBPS/Text/part0036.xhtml
17181 2019-06-27 10:40 OEBPS/Text/part0037.xhtml
5238 2019-06-27 10:40 OEBPS/Text/part0038.xhtml
561 2019-06-27 10:40 OEBPS/Text/part0039.xhtml
644 2019-06-27 10:40 OEBPS/Text/part0040.xhtml
1163 2019-06-27 10:40 OEBPS/Text/part0041.xhtml
1473 2019-06-27 10:40 OEBPS/Text/part0042.xhtml
38427 2019-06-27 10:40 OEBPS/Text/part0043.xhtml
90589 2019-06-27 10:40 OEBPS/Text/part0044.xhtml
51278 2019-06-27 10:40 OEBPS/Text/part0045.xhtml
58321 2019-06-27 10:40 OEBPS/Text/part0046.xhtml
29670 2019-06-27 10:40 OEBPS/Text/part0047.xhtml
12903 2019-06-27 10:40 OEBPS/Text/part0048.xhtml
7364 2019-06-27 10:40 OEBPS/toc.ncx
--------- -------
2422768 72 files
同时可以直接解压:
[littleye233@lymjrolt Downloads]$ unzip test.epub -d test_epub
Archive: test.epub
extracting: test_epub/mimetype
inflating: test_epub/META-INF/container.xml
inflating: test_epub/OEBPS/content.opf
inflating: test_epub/OEBPS/Images/cover00464.jpeg
inflating: test_epub/OEBPS/Images/image00456.jpeg
inflating: test_epub/OEBPS/Images/image00457.jpeg
inflating: test_epub/OEBPS/Images/image00458.jpeg
inflating: test_epub/OEBPS/Images/image00459.jpeg
inflating: test_epub/OEBPS/Images/image00460.jpeg
inflating: test_epub/OEBPS/Images/image00461.jpeg
inflating: test_epub/OEBPS/Images/image00462.jpeg
inflating: test_epub/OEBPS/Images/image00463.jpeg
inflating: test_epub/OEBPS/Styles/style0001.css
inflating: test_epub/OEBPS/Styles/style0002.css
inflating: test_epub/OEBPS/Styles/style0003.css
inflating: test_epub/OEBPS/Styles/style0004.css
inflating: test_epub/OEBPS/Styles/style0005.css
inflating: test_epub/OEBPS/Styles/style0006.css
inflating: test_epub/OEBPS/Styles/style0007.css
inflating: test_epub/OEBPS/Styles/style0008.css
inflating: test_epub/OEBPS/Styles/style0009.css
inflating: test_epub/OEBPS/Text/cover_page.xhtml
inflating: test_epub/OEBPS/Text/part0000.xhtml
inflating: test_epub/OEBPS/Text/part0001.xhtml
inflating: test_epub/OEBPS/Text/part0002.xhtml
inflating: test_epub/OEBPS/Text/part0003.xhtml
inflating: test_epub/OEBPS/Text/part0004.xhtml
inflating: test_epub/OEBPS/Text/part0005.xhtml
inflating: test_epub/OEBPS/Text/part0006.xhtml
inflating: test_epub/OEBPS/Text/part0007.xhtml
inflating: test_epub/OEBPS/Text/part0008.xhtml
inflating: test_epub/OEBPS/Text/part0009.xhtml
inflating: test_epub/OEBPS/Text/part0010.xhtml
inflating: test_epub/OEBPS/Text/part0011.xhtml
inflating: test_epub/OEBPS/Text/part0012.xhtml
inflating: test_epub/OEBPS/Text/part0013.xhtml
inflating: test_epub/OEBPS/Text/part0014.xhtml
inflating: test_epub/OEBPS/Text/part0015.xhtml
inflating: test_epub/OEBPS/Text/part0016.xhtml
inflating: test_epub/OEBPS/Text/part0017.xhtml
inflating: test_epub/OEBPS/Text/part0018.xhtml
inflating: test_epub/OEBPS/Text/part0019.xhtml
inflating: test_epub/OEBPS/Text/part0020.xhtml
inflating: test_epub/OEBPS/Text/part0021.xhtml
inflating: test_epub/OEBPS/Text/part0022.xhtml
inflating: test_epub/OEBPS/Text/part0023.xhtml
inflating: test_epub/OEBPS/Text/part0024.xhtml
inflating: test_epub/OEBPS/Text/part0025.xhtml
inflating: test_epub/OEBPS/Text/part0026.xhtml
inflating: test_epub/OEBPS/Text/part0027.xhtml
inflating: test_epub/OEBPS/Text/part0028.xhtml
inflating: test_epub/OEBPS/Text/part0029.xhtml
inflating: test_epub/OEBPS/Text/part0030.xhtml
inflating: test_epub/OEBPS/Text/part0031.xhtml
inflating: test_epub/OEBPS/Text/part0032.xhtml
inflating: test_epub/OEBPS/Text/part0033.xhtml
inflating: test_epub/OEBPS/Text/part0034.xhtml
inflating: test_epub/OEBPS/Text/part0035.xhtml
inflating: test_epub/OEBPS/Text/part0036.xhtml
inflating: test_epub/OEBPS/Text/part0037.xhtml
inflating: test_epub/OEBPS/Text/part0038.xhtml
inflating: test_epub/OEBPS/Text/part0039.xhtml
inflating: test_epub/OEBPS/Text/part0040.xhtml
inflating: test_epub/OEBPS/Text/part0041.xhtml
inflating: test_epub/OEBPS/Text/part0042.xhtml
inflating: test_epub/OEBPS/Text/part0043.xhtml
inflating: test_epub/OEBPS/Text/part0044.xhtml
inflating: test_epub/OEBPS/Text/part0045.xhtml
inflating: test_epub/OEBPS/Text/part0046.xhtml
inflating: test_epub/OEBPS/Text/part0047.xhtml
inflating: test_epub/OEBPS/Text/part0048.xhtml
inflating: test_epub/OEBPS/toc.ncx
为了更清楚地显示文件树结构,我们也可以使用 tree
命令 (这个命令在 Windows 中是内置的,在 Linux 中需要安装 tree
这个包,使用软件包管理器或编译安装均可) :
[littleye233@lymjrolt test_epub]$ tree
.
├── META-INF
│ └── container.xml
├── mimetype
└── OEBPS
├── content.opf
├── Images
│ ├── cover00464.jpeg
│ ├── image00456.jpeg
│ ├── image00457.jpeg
│ ├── image00458.jpeg
│ ├── image00459.jpeg
│ ├── image00460.jpeg
│ ├── image00461.jpeg
│ ├── image00462.jpeg
│ └── image00463.jpeg
├── Styles
│ ├── style0001.css
│ ├── style0002.css
│ ├── style0003.css
│ ├── style0004.css
│ ├── style0005.css
│ ├── style0006.css
│ ├── style0007.css
│ ├── style0008.css
│ └── style0009.css
├── Text
│ ├── cover_page.xhtml
│ ├── part0000.xhtml
│ ├── part0001.xhtml
│ ├── part0002.xhtml
│ ├── part0003.xhtml
│ ├── part0004.xhtml
│ ├── part0005.xhtml
│ ├── part0006.xhtml
│ ├── part0007.xhtml
│ ├── part0008.xhtml
│ ├── part0009.xhtml
│ ├── part0010.xhtml
│ ├── part0011.xhtml
│ ├── part0012.xhtml
│ ├── part0013.xhtml
│ ├── part0014.xhtml
│ ├── part0015.xhtml
│ ├── part0016.xhtml
│ ├── part0017.xhtml
│ ├── part0018.xhtml
│ ├── part0019.xhtml
│ ├── part0020.xhtml
│ ├── part0021.xhtml
│ ├── part0022.xhtml
│ ├── part0023.xhtml
│ ├── part0024.xhtml
│ ├── part0025.xhtml
│ ├── part0026.xhtml
│ ├── part0027.xhtml
│ ├── part0028.xhtml
│ ├── part0029.xhtml
│ ├── part0030.xhtml
│ ├── part0031.xhtml
│ ├── part0032.xhtml
│ ├── part0033.xhtml
│ ├── part0034.xhtml
│ ├── part0035.xhtml
│ ├── part0036.xhtml
│ ├── part0037.xhtml
│ ├── part0038.xhtml
│ ├── part0039.xhtml
│ ├── part0040.xhtml
│ ├── part0041.xhtml
│ ├── part0042.xhtml
│ ├── part0043.xhtml
│ ├── part0044.xhtml
│ ├── part0045.xhtml
│ ├── part0046.xhtml
│ ├── part0047.xhtml
│ └── part0048.xhtml
└── toc.ncx
5 directories, 72 files
Round IV. 内部文件
到这里我们大概就能猜出来:
META-INF
文件夹:里面存放的应该是“容器” (也就是这个 ePub 文件) 的相关配置文件;mimetype
文件:里面定义了这个文件的类型为 “ePub” (其中 “MIME” 是 “Multipurpose Internet Mail Extensions” 的缩写,从字面上也能看出其具有指示 “Extension” 的机能) ;OEBPS
文件夹:虽暂不知其确切含义,但应存放 ePub 的文字、图片以及其他的界面数据;content.opf
文件:里面存放的应该是目录信息——或是定义各种文件的“次序”;Images
Styles
和Text
文件夹:明显分别存放图片、层叠样式表和文字数据;toc.ncx
文件:可能是真正的目录 (“toc” 是 “table of contents” 的缩写)。
接下来我们将挨个分析。
Round IV.I. 容器
先看 META-INF/container.xml
:
[littleye233@lymjrolt test_epub]$ file META-INF/container.xml
META-INF/container.xml: XML 1.0 document, ASCII text
输出其内容:
<?xml version="1.0" encoding="UTF-8"?>
<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
<rootfiles>
<rootfile full-path="OEBPS/content.opf" media-type="application/oebps-package+xml"/> </rootfiles>
</container>
显然是一个标准的 XML 文件,其中我们可以注意到 /container/rootfiles/rootfile[@class='full-path']
[1] 中定义了一个我们之前认定的目录文件,但此处可以规范化,故这个文件在大多数 ePub 档中应该是相同的。
Round IV.II. 文件类型定性
接下来看 mimetype
文件:
[littleye233@lymjrolt test_epub]$ cat mimetype
application/epub+zip
这也是相当显然的,也不再赘述。
Round IV.III. 目录?
再看 OEBPS/content.opf
:
[littleye233@lymjrolt test_epub]$ file OEBPS/content.opf
OEBPS/content.opf: XML 1.0 document, Unicode text, UTF-8 text, with very long lines (504)
这也是一个 XML 文件,令人惊讶的是 file
命令竟能看出这个文件中最长的行有 504 个字符,属实让人害怕。
说明我之前并没有猜错,这个文件存放的是超越“目录”的东西,而是“次序”——更进一步说。是“索引”。这个文件类似于其他文件格式或目录树中的 index.*
,将 ePub 中的各种数据编上号码,同时这里也定义了标题、语言、作者、出版 (发布) 日期等元信息。至于之前看到的超长行,似乎是一种十六进制的水印 (watermark) ,或许是为了防侵权等。
其中的 /package/manifest/item
定义了所有的索引,以及文件对应的类型; /package/spine/itemref
暂不知进一步的作用,但从中可看出能定义是否“线性” (linear) ; /package/guide/reference
定义了 ePub 的封面等索引,可供文件管理器和 ePub 阅读器使用 (显示预览页) 。
Round IV.IV. 目录!
再看 OEBPS/toc.ncx
:
[littleye233@lymjrolt test_epub]$ file OEBPS/toc.ncx
OEBPS/toc.ncx: XML 1.0 document, Unicode text, UTF-8 text
感觉再讨论文件类型已经无关紧要了。再次查看内容:
我们不妨将目光转向较为重要的“目录”的定义上。为了方便观察,笔者偷点懒,使用桌面环境中自带的阅读器观察:
从中可以看出目录是二层结构,恰好和 OEBPS/toc.ncx
中的定义保持一致。而其中的部分重要属性均可“望文生义”,此处不再进一步研究。
Round IV.V. 其余部分
最后剩下的是图片、文字和层叠样式表。虽然这部分是在整个 ePub 文件中占比最大也可以说是最重要的部分,但由于这一块的内容实在是太过直白,再讲下去恐怕要开始补习 HTML 和 CSS 知识了,故同样略去。
总结
根据上文中的简要探究, ePub 是一种以 XML 文件格式为配置文件类型的、包含有图片及文字等数据的、以压缩档为本质的文件格式。查阅相关资料后可知其实质与上文中分析类似。
而通过上文的分析,我们初步体验到分析一种陌生文件格式的规律和技巧,可以用于后续对更复杂的文件格式的探究。
但最后,别忘了把那个 ePub 文件的名字改回来 XD :
[littleye233@lymjrolt Downloads]$ mv test.epub 咖啡馆推理事件簿系列(全四本).epub
【完】
脚注
此处为 XPath 语法,用于描述类 XML 文件各种元素的位置,后文类似者不再注明。 ↩︎