You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 19:15:00