跳轉到內容

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>user@example.com</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

此報告構成了您元素清單的基礎,您將使用它作為型別切換轉換的基礎。請注意,書籍的前言和後記中的一些元素未包含在此列表中,並且還請注意屬性轉換是在元素級別函式中處理的。

參考文獻

[編輯 | 編輯原始碼]
華夏公益教科書