Haskell zip包制作EPUB时如何将mimetype设为压缩包首个文件
我刚开始使用Haskell的zip包创建.epub文件,使用体验远优于此前尝试的JavaScript库epub-zipper。
但我遇到了一个无法解决的细节问题:EPUB 3.2规范明确规定:
OCF ZIP容器中的第一个文件必须是mimetype文件
同时该文件不得被压缩。我已经实现了“不压缩”的要求,但不知道怎么满足“mimetype为压缩包首个文件”的条件,我的初始代码如下:
main :: IO () main = do mimetype <- mkEntrySelector "mimetype" createArchive "conmocion.epub" $ do packDirRecur Deflate (\s -> mkEntrySelector $ "META-INF/" ++ s) "./ePub/META-INF" packDirRecur Deflate (\s -> mkEntrySelector $ "es/" ++ s) "./ePub/es" loadEntry Store mimetype "./ePub/mimetype"
提交到EPUB在线验证器校验时得到如下报错:
Mimetype file entry is missing or is not the first file in the archive.
请问有什么方法可以将mimetype文件设置为ZIP压缩包的第一个文件?
更新
调整后的代码如下:
{-# LANGUAGE OverloadedStrings #-} module Main where import Codec.Archive.Zip import qualified Data.Map as M main :: IO () main = do mimetype <- mkEntrySelector "000mimetype" createArchive "conmocion.epub" $ contents mimetype createArchive "conmocion.zip" $ contents mimetype where contents x = do loadEntry Store x "./ePub/mimetype" packDirRecur Deflate (mkEntrySelector . ("META-INF/" ++)) "./ePub/META-INF" packDirRecur Deflate (mkEntrySelector . ("es/" ++) ) "./ePub/es" packDirRecur Deflate (mkEntrySelector . ("assets/" ++) ) "./ePub/assets" test :: IO () test = do entries <- withArchive "conmocion.epub" (M.keys <$> getEntries) mapM_ print entries
调用test函数后输出内容如下:
ghci> test "000mimetype" "assets\\all.css" "es\\chapters\\c1.xhtml" "es\\chapters\\c2.xhtml" "es\\chapters\\c3.xhtml" "es\\chapters\\c4.xhtml" "es\\chapters\\c5.xhtml" "es\\chapters\\c6.xhtml" "es\\chapters\\c7.xhtml" "es\\chapters\\c8.xhtml" "es\\chapters\\c9.xhtml" "es\\docs\\colophon.xhtml" "es\\docs\\coverback.xhtml" "es\\docs\\coverfront.xhtml" "es\\docs\\epigraph.xhtml" "es\\docs\\legal.xhtml" "es\\docs\\nav.xhtml" "es\\docs\\notice.xhtml" "es\\docs\\thanks.xhtml" "es\\es.opf" "META-INF\\container.xml"
解压压缩包后可以找到000mimetype文件,其内容仅为application/epub+zip一行。
补充规范
以下摘自EPUB Open Container Format (OCF) 3.2相关规定:
4.3 OCF ZIP容器媒体类型标识 OCF ZIP容器中的第一个文件必须是mimetype文件,且满足以下要求: 1. mimetype文件的内容必须是US-ASCII编码的MIME媒体类型字符串`application/epub+zip` 2. mimetype文件不得包含任何前导、尾随的填充字符或空白字符 3. mimetype文件不得以Unicode字节顺序标记U+FEFF开头 4. mimetype文件不得被压缩或加密,其ZIP头中不得存在额外字段
解决方案
你遇到的问题根源是Codec.Archive.Zip库默认会对zip包内的文件按文件名的字典序排序,ASCII编码中大写字母M的排序优先级高于小写字母m,所以哪怕你把loadEntry写在最前面,META-INF目录下的文件还是会排在mimetype前面。
你调整后的代码给文件名加了000前缀解决了排序问题,但违反了规范要求的文件名必须为mimetype的规则,验证器依然会报错。
最稳妥的解决方法是分两步生成epub文件,先写入mimetype再追加其他内容,从根源上保证mimetype是第一个文件:
main :: IO () main = do mimetype <- mkEntrySelector "mimetype" -- 第一步:创建仅包含mimetype的epub文件 createArchive "conmocion.epub" $ do loadEntry Store mimetype "./ePub/mimetype" -- 第二步:追加其他目录到已有的epub文件 withArchive "conmocion.epub" $ do packDirRecur Deflate (\s -> mkEntrySelector $ "META-INF/" ++ s) "./ePub/META-INF" packDirRecur Deflate (\s -> mkEntrySelector $ "es/" ++ s) "./ePub/es" packDirRecur Deflate (\s -> mkEntrySelector $ "assets/" ++ s) "./ePub/assets"
这种方式生成的文件完全符合EPUB3.2规范,提交到验证器不会再报错。
内容的提问来源于stack exchange,提问作者RamiroPastor

