跳转到内容

XQuery/DocBook 到 ePub

来自维基教科书,开放世界中的开放书籍

您想将 DocBook 5 文档转换为 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 文件生成器

[编辑 | 编辑源代码]

为了演示示例 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 文件,但也可以包含。

在集合中存储您的 ePub 文件

[编辑 | 编辑源代码]

创建条目列表后,现在可以将条目直接存储在单个 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 文件存储在数据库中的二进制文件中。您可以根据需要直接将任何 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 中呈现的屏幕截图。

Sceen Image of ePub in FireFox
ePub 在 FireFox 中的屏幕截图

示例:转换 DocBook 章

[编辑 | 编辑源代码]

虽然必须做一些工作才能将书籍的前后部分转换为 ePub 格式,但此示例中书籍创建的核心将是每章处理以构建 ePub “书籍”。但是,您不必使用 docbook chapter 元素来创建 ePub 的各个部分。这可以使用书籍 partssectionsect1 或您想要使用的任何其他 docbook 元素来完成。如果您确实像此示例中的章节一样使用章节,那么以下是转换为 ePub 格式的主要逻辑

对于每个章节,我们必须添加

  1. OEBPS/content.opf 文件中,我们将在 <manifest> 部分添加一个 <item> XML 元素,并在 <spine> 元素中添加一个 <itemref>
  2. OEBPS/toc.ncx 文件中添加一个 <navPoint> XML 元素用于导航
  3. 在主序列中为每个章节添加一个 <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 &amp; 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

此报告构成了您元素清单的基础,您将使用它作为类型切换转换的基础。请注意,书籍的前言和后记中的一些元素未包含在此列表中,并且还请注意属性转换是在元素级别函数中处理的。

参考文献

[编辑 | 编辑源代码]
华夏公益教科书