XQuery/DocBook 到 ePub
我们将创建一个 XQuery 类型切换转换,它将执行此转换。请注意,不需要任何 XSLT。如果您熟悉 XQuery,则无需学习任何新的转换语言。
此转换的基础将是一个中心 XQuery 模块,该模块具有一个主要调度函数,该函数将使用类型切换运算符来实现调度模式。主函数将查看每个元素,然后调用相应的函数。这使得转换易于编写和维护。主函数将创建一个单个大型 XML 文件,然后将其转换为包含一些附加书籍元数据的 zip 文件。然后可以使用 ePub 验证工具测试此 zip 文件是否符合 ePub 格式规则。
我们将使用的 zip 函数是 compression:zip() 函数,该函数在 此处 有记录。有人可能认为,解决此问题的方法是将所有正确的文档放在 eXist 集合中,然后将此集合传递给 zip 函数。不幸的是,这种方法有两个问题。第一个是 zip 函数的当前实现不允许您在集合设置中指定相对路径,第二个是 ePub 格式对文件在 ePub 文件中出现的顺序非常严格。例如,指示 mime 类型的文本文件必须是 zip 容器中的第一个文件。
出于这些原因,我们必须将 zip 函数传递给一个 <entry> 元素的序列,这些元素必须以非常严格的顺序排列。格式为
let $entries := (<entry name=""/>, <entry name=""/> <entry name=""/>...) return compression:zip($entries , true())
注意:此转换的最后一步只会在 eXist 1.5 上起作用。“zip”压缩函数的新功能将不会在 eXist 1.4 上起作用。
为了演示示例 ePub 文件的确切格式,这里是在单个 XML 文档中对整个文件的“序列化”
用于 ePub Zip 文件包的文件条目
(: create a sequence of entries for the zip program to use :)
let $entries :=
(
<entry name="mimetype" type="text" method="store">application/epub+zip</entry>,
<entry name="META-INF/container.xml" type="xml">
<container xmlns="urn:oasis:names:tc:opendocument:xmlns:container" version="1.0">
<rootfiles>
<rootfile full-path="OEBPS/content.opf" media-type="application/oebps-package+xml"/>
</rootfiles>
</container>
</entry>,
<entry name="OEBPS/toc.ncx" type="xml">
<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1">
<head>
<meta name="dtb:uid" content="http://www.danmccreary.com/books/epub-test"/>
<meta name="dtb:depth" content="1"/>
<meta name="dtb:totalPageCount" content="0"/>
<meta name="dtb:maxPageNumber" content="0"/>
</head>
<docTitle>
<text>My Book Title</text>
</docTitle>
<navMap>
<navPoint id="title-page" playOrder="1">
<navLabel>
<text>Test Page</text>
</navLabel>
<content src="test.xhtml"/>
</navPoint>
<navPoint id="chapter-1" playOrder="2">
<navLabel>
<text>Chapter 1</text>
</navLabel>
<content src="chapter-1.xhtml"/>
</navPoint>
</navMap>
</ncx>
</entry>,
<entry name="OEBPS/content.opf" type="xml">
<package xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns="http://www.idpf.org/2007/opf" unique-identifier="bookid" version="2.0">
<metadata>
<dc:title>My Book Title</dc:title>
<dc:creator>Dan McCreary</dc:creator>
<dc:identifier id="bookid">http://www.danmccreary.com/books/epub-test</dc:identifier>
<dc:language>en-US</dc:language>
</metadata>
<manifest>
<item id="ncx" href="toc.ncx" media-type="application/x-dtbncx+xml"/>
<item id="title-page" href="title-page.xhtml" media-type="application/xhtml+xml"/>
<item id="chapter-1" href="chapter-1.xhtml" media-type="application/xhtml+xml"/>
</manifest>
<spine toc="ncx">
<itemref idref="title-page" />
<itemref idref="chapter-1" />
</spine>
</package>
</entry>,
<entry name="OEBPS/title-page.xhtml" type="xml">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Title Page</title>
</head>
<body>
<h1>Title Page</h1>
<p>Text for paragraph 1</p>
<p>Text for paragraph 2</p>
</body>
</html>
</entry>,
<entry name="OEBPS/chapter-1.xhtml" type="xml">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Chapter 1</title>
</head>
<body>
<h1>Chapter 1</h1>
<p>Text for paragraph 1</p>
<p>Text for paragraph 2</p>
</body>
</html>
</entry>
)
在本文中,我们不会花费大量时间来解释 ePub 文件的确切格式。简而言之,有一些“常量”,例如 mime 类型文件和 container.xml 文件,它们不会改变。其他文件用于描述 zip 文件的解压缩方式以及文件的目录结构应该是什么样子。从那时起,书中的每一章本质上都是一个 XHTML 文件,其中包含用于头部、主体、标题和段落的标准元素。上面的示例不包含 CSS 文件,但也可以包含。
创建条目列表后,现在可以将条目直接存储在单个 zip 文件中。这里有一个小型实用程序函数,它将条目存储到集合中的文件
declare function epub-util:store-entries-in-epub($entries as element(entry)*, $collection as xs:string, $file-name as xs:string) as node() {
let $zip-file := compression:zip($entries, true())
let $file-name-suffix :=
if (ends-with($file-name, '.epub'))
then $file-name
else concat($file-name, '.epub')
let $store := xmldb:store($collection, $file-name-suffix, $zip-file, 'application/epub+zip')
return
<result>
<message>{count($entries)} entries stored in {$collection}/{$file-name-suffix}</message>
</result>
};
此版本将检查以确保文件具有 .epub 后缀,并将确保文件以正确的 mime 类型存储在文件中。
无需将 ePub 文件存储在数据库中的二进制文件中。您可以根据需要直接将任何 ePub 文件动态渲染到您的网络浏览器,就像生成任何网页一样。
以下 XQuery 可用于在您的网络浏览器中查看 ePub 文件
declare function epub-util:render-epub($entries as node()*) {
let $zip-file := compression:zip($entries, true())
return response:stream-binary($zip-file, 'application/epub+zip')
};
请注意,您必须不要在此函数上放置返回类型。它返回一个二进制文件,并且不要将其转换为 item() 或 node()。这很重要。
此函数不仅压缩文件,还向浏览器返回一个二进制流,该流设置了 mime 类型,以便如果您的浏览器具有 ePub 查看器,它将直接在查看器中渲染。
事实证明,这实际上是向用户生成文档的非常有效的方法。所有章节都压缩到一个压缩文件中,然后直接在浏览器中解压缩。
下图是在安装了免费的 EPUBReader 插件后,测试 ePub 文件在 FireFox 中呈现的屏幕截图。
虽然必须做一些工作才能将书籍的前后部分转换为 ePub 格式,但此示例中书籍创建的核心将是每章处理以构建 ePub “书籍”。但是,您不必使用 docbook chapter 元素来创建 ePub 的各个部分。这可以使用书籍 parts、section、sect1 或您想要使用的任何其他 docbook 元素来完成。如果您确实像此示例中的章节一样使用章节,那么以下是转换为 ePub 格式的主要逻辑
对于每个章节,我们必须添加
- 到 OEBPS/content.opf 文件中,我们将在 <manifest> 部分添加一个 <item> XML 元素,并在 <spine> 元素中添加一个 <itemref>
- 在 OEBPS/toc.ncx 文件中添加一个 <navPoint> XML 元素用于导航
- 在主序列中为每个章节添加一个 <entry>,用于该章节的 <xhtml> 文件
以下是这三个项目的伪代码
在 db2epub:package-entry 函数中
以下将为每个章节添加一个项目
<manifest>
...
{
for $chapter at $count in collection($root)//db:chapter
return
<item id="chapter-{$count}" href="chapter-{$count}.xhtml" media-type="application/xhtml+xml"/>
}</manifest>
以下将引用此章节,如果该章节将在脊柱的目录中列出。
<spine toc="ncx">
...
{for $chapter at $count in collection($root)//db:chapter
return
<itemref idref="chapter-{$count}"/>
}
</spine>
在 chapter-navmap() 函数中
<navMap>
...
{for $chapter at $count in collection($root)//db:chapter
return
<navPoint id="chapter-{$count}" playOrder="1">
<navLabel>
<text>Chapter {$count}</text>
</navLabel>
<content src="chapter-{$count}.xhtml"/>
</navPoint>
}</navmap>
在 chapter-entries() 函数中
for $chapter at $count in collection($root)//db:chapter
return
<entry name="chapter-{$count}"> type="xml">{db:chapter-to-xhtml($chapter)}</entry>
请注意,您必须提供一个函数来将每个章节转换为 XHTML 文件。但此函数通常已经在 docbook-to-html() 模块中编写了。
<book xmlns="http://docbook.org/ns/docbook"
xmlns:xlink="http://www.w3.org/1999/xlink" version="5.0">
<info>
<title>How to Transform DocBook to ePub Format with XQuery</title>
<author>
<orgname>Kelly McCreary & Associates</orgname>
<address>
<city>Minneapolis</city>
<state>MN</state>
<country>USA</country>
</address>
<email>[email protected]</email>
</author>
</info>
<part>
<title>First Part</title>
<subtitle>Subtitle of First Part</subtitle>
<chapter>
<title>Chapter Title</title>
<subtitle>Subtitle of Chapter</subtitle>
<sect1>
<title>Section1 Title</title>
<subtitle>Subtitle of Section 1</subtitle>
<para>Text</para>
</sect1>
</chapter>
</part>
</book>
接下来我们将展示如何将每个章节中的元素转换为 XHTML ePub 部分。我们的第一步是获取源 DocBook 文档中章节中使用的所有元素名称的列表。这可以使用以下 XPath 表达式来完成
let $distinct-chapter-element-names := distinct-values(/db:book//db:chapter/descendant-or-self::*/name(.))
“descendant-or-self” XPath 轴表达式与使用 //*/name(.) 非常相似,但也包括根节点。您可以通过将其放入带有添加的 order by 子句的 FLWOR 语句中来对该列表进行排序
let $sorted-element-names := for $element-name in $distinct-chapter-element-names order by $element-name return $element-name
此报告构成了您元素清单的基础,您将使用它作为类型切换转换的基础。请注意,书籍的前言和后记中的一些元素未包含在此列表中,并且还请注意属性转换是在元素级别函数中处理的。
- DocBook
- ePub 的维基百科页面
- 国际数字出版论坛网站,管理 ePub 规范的人。
- 开放出版结构 (OPS) 2.0.1 v1.0v1.0.1 元素定义
- Jedisaber.com 提供许多关于 ePub 的资源,包括示例 ePub 书籍列表
- [1] TEI 到 ePub 示例