PageRenderTime 61ms CodeModel.GetById 35ms RepoModel.GetById 0ms app.codeStats 0ms

/source/_posts/2011-04-25-parsing-and-ebook-making.markdown

https://github.com/wjlroe/wjlroe.github.com
Markdown | 184 lines | 151 code | 33 blank | 0 comment | 0 complexity | dbb9d66b5d3e4ec0b4738553bd26634e MD5 | raw file
  1. ---
  2. layout: post
  3. title: Parsing and ebook making
  4. date: 2011-04-25
  5. categories: [ebooks, clojure]
  6. comments: true
  7. ---
  8. I have just finished a 6 hour hacking session on [parse_perseus](http://github.com/wjlroe/parse_perseus). The aims were to fix most of the encoding problems, split the content up into books and create a table of contents. Other than a few problems with encoding, I managed to complete all that.
  9. ## Tables of contents
  10. The table of contents was a bit strange. The Kindle's mobi format pretty much ignores the epub standard .ncx file (which is an XML file that contains `<navPoint>` elements that get mapped to entries in a generated table of contents - Adobe Digital Editions uses this for example). Here's an abridged version of the NCX file I had generated:
  11. {% codeblock lang:xml %}
  12. <?xml version="1.0" encoding="UTF-8"?>
  13. <!DOCTYPE ncx PUBLIC "-//NISO//DTD ncx 2005-1//EN"
  14. "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd">
  15. <ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1" xml:lang="en">
  16. <head>
  17. <meta name="dtb:uid" content="http://en.wikipedia.org/wiki/The_Odyssey"/>
  18. <meta name="dtb:depth" content="1"/>
  19. <meta name="dtb:totalPageCount" content="0"/>
  20. <meta name="dtb:maxPageNumber" content="0"/>
  21. </head>
  22. <docTitle>
  23. <text>Ὀδύσσεια</text>
  24. </docTitle>
  25. <navMap>
  26. <navPoint id="toc" playOrder="0">
  27. <navLabel>
  28. <text>Table of Contents</text>
  29. </navLabel>
  30. <content src="toc.html"/>
  31. </navPoint>
  32. <navPoint class="chapter" id="book-1" playOrder="1">
  33. <navLabel>
  34. <text>Book 1</text>
  35. </navLabel>
  36. <content src="book-1.xhtml"/>
  37. </navPoint>
  38. </navMap>
  39. </ncx>
  40. {% endcodeblock %}
  41. There were obviously more chapters/books than that. The Kindle/mobi format doesn't use this for the table of contents, so when you open a converted epub on a Kindle (or using the Kindle desktop software), the menu item to go to the table of contents is greyed out. After digging around on the web I discovered that the mobi format uses a toc file, which is basically an html file full of links. In order for that to work, you need to reference the toc file in the OPF file. Here's an abbreviated version of the one I generated:
  42. {% codeblock lang:xml %}
  43. <?xml version="1.0" encoding="UTF-8"?>
  44. <package xmlns="http://www.idpf.org/2007/opf" version="2.0"
  45. unique-identifier="odyssey_gk">
  46. <metadata xmlns:dc="http://purl.org/dc/elements/1.1/"
  47. xmlns:dcterms="http://purl.org/dc/terms/"
  48. xmlns:opf="http://www.idpf.org/2007/opf"
  49. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  50. <dc:title>Ὀδύσσεια</dc:title>
  51. <dc:language xsi:type="dcterms:RFC3066">en-us</dc:language>
  52. <dc:identifier id="odyssey_gk" opf:scheme="URL">
  53. http://en.wikipedia.org/wiki/The_Odyssey
  54. </dc:identifier>
  55. <dc:creator opf:file-as="Homer" opf:role="aut">Homer</dc:creator>
  56. <meta name="cover" content="cover-image"/>
  57. </metadata>
  58. <manifest>
  59. <item id="book-1" href="book-1.xhtml" media-type="application/xhtml+xml"/>
  60. <item id="stylesheet" href="style.css" media-type="text/css"/>
  61. <item id="ncx" href="book.ncx" media-type="application/x-dtbncx+xml"/>
  62. <item id="cover" href="cover.html" media-type="application/xhtml+xml"/>
  63. <item id="toc" href="toc.html" media-type="application/xhtml+xml"/>
  64. <item id="cover-image" href="cover.jpg" media-type="image/jpeg"/>
  65. </manifest>
  66. <spine toc="ncx">
  67. <itemref idref="cover" linear="no"/>
  68. <itemref idref="toc" linear="no"/>
  69. <itemref idref="book-1"/>
  70. </spine>
  71. <guide>
  72. <reference href="cover.html" type="cover" title="Cover"/>
  73. <reference href="toc.html" type="toc" title="Table of Contents"/>
  74. <reference href="book-1.xhtml" type="text" title="Text"/>
  75. </guide>
  76. </package>
  77. {% endcodeblock %}
  78. The important bit there is the `<reference>` element inside `<guide>` that is of type "toc". After that, the actual table of contents file is pretty straightforward:
  79. {% codeblock lang:xml %}
  80. <?xml version="1.0"?>
  81. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
  82. "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
  83. <html xmlns="http://www.w3.org/1999/xhtml">
  84. <head>
  85. <title>Table of Contents</title>
  86. <style type="text/css">img { max-width: 100%; height: 100% }</style>
  87. </head>
  88. <body>
  89. <div id="contents">
  90. <h2>Contents</h2>
  91. <ul>
  92. <li>
  93. <a href="book-1.xhtml">Book 1</a>
  94. </li>
  95. </ul>
  96. </div>
  97. </body>
  98. </html>
  99. {% endcodeblock %}
  100. That's just a standard content file and can be formatted, using CSS, the way you want it rendered on the e-reader.
  101. ## Encoding
  102. I managed to solve the problem I was having rendering the correct unicode for Iota Dialytika Tonos. I don't expect anyone to know what that is, I didn't and it's still just a character to me. But it looks like this:
  103. <h1 class="greek">ΐ</h1>
  104. <aside class="post">
  105. <strong>Betacode</strong> is the ASCII language used by Perseus Digital library to represent Greek characters easily. It's used in their opensource XML files (that I've used) and as a way to search Greek books (due to the unavailability of Ancient Greek keyboards). So <code>a)/ndra</code> becomes <code class="greek">ἄνδρα</code>.
  106. </aside>
  107. In betacode, this is written as `i/+`. Normally, the parser encodes the character `i`, then each diacritic in turn: `/`, then `+` into combining diacritics. This resulted in characters `0x03b9 0x0301 0x0308`, which would be normalized using Java's Normalizer/normalize function as:
  108. <h1 class="greek">ί̈</h1>
  109. The frustrating thing is that it's quite difficult to tell the difference on the terminal, when running tests - even when I'd bumped the font size up to crazy levels.
  110. In order to match that symbol, I created the following rule:
  111. {% codeblock lang:clojure %}
  112. (def iota-dialytika-tonos
  113. (constant-semantics (lit-conc-seq "i/+" lit)
  114. (char 0x0390)))
  115. {% endcodeblock %}
  116. Then I'd redefined `character` to be:
  117. {% codeblock lang:clojure %}
  118. (def character (alt iota-dialytika-tonos final-sigma upper-char lower-char))
  119. {% endcodeblock %}
  120. Which means that the rule `iota-dialytika-tonos` takes precedence over any other character (to prevent the dumb combining diacritic rules from matching it).
  121. And that brings me on to final sigma. Final sigma has given me quite a lot of pain, ever since I started building this parser. For those of you who don't know Greek, like me, they have two sigma characters - one is used in the middle of a word:
  122. <h1 class="greek">σ</h1>
  123. But if sigma is at the end of a word, it must use the final sigma character, thus:
  124. <h1 class="greek">ς</h1>
  125. The problem with matching something at the end of a word is that fnparse's matchers are mostly greedy. There's no equivalent rule, as far as I can tell, to the regular expression `(.+)s`. The `rep+` rules always greedily gobble all the tokens, including the last `s`. At the moment, all I have is the following rule for final sigma:
  126. {% codeblock lang:clojure %}
  127. (def final-sigma (constant-semantics (lit-conc-seq "s " lit)
  128. (str (char (- (beta-char-to-greek-char \s) 1))
  129. \space)))
  130. {% endcodeblock %}
  131. This isn't ideal because it can't match either the last `s` of the last word in a string (or line), or `s` followed only by a non-character (such as `:`). This is something I have to work on, but fnparse isn't making it easy for me here.
  132. So as a result of all that, here's the first verse of the Odyssey by Homer:
  133. <blockquote class="greek" style="font-style: normal">
  134. ἄνδρα μοι ἔννεπε, μοῦσα, πολύτροπον, ὃς μάλα πολλὰ<br/>
  135. πλάγχθη, ἐπεὶ Τροίης ἱερὸν πτολίεθρον ἔπερσεν:<br/>
  136. πολλῶν δ ἀνθρώπων ἴδεν ἄστεα καὶ νόον ἔγνω,<br/>
  137. πολλὰ δ γ ἐν πόντῳ πάθεν ἄλγεα ὃν κατὰ θυμόν,<br/>
  138. ἀρνύμενος ἥν τε ψυχὴν καὶ νόστον ἑταίρων.<br/>
  139. ἀλλ οὐδ ὣς ἑτάρους ἐρρύσατο, ἱέμενός περ:<br/>
  140. αὐτῶν γὰρ σφετέρῃσιν ἀτασθαλίῃσιν ὄλοντο,<br/>
  141. νήπιοι, οἳ κατὰ βοῦς Ὑπερίονος Ἠελίοιο<br/>
  142. ἤσθιον: αὐτὰρ τοῖσιν ἀφείλετο νόστιμον ἦμαρ.<br/>
  143. τῶν ἁμόθεν γε, θεά, θύγατερ Διός, εἰπὲ καὶ ἡμῖν.
  144. </blockquote>
  145. - - -
  146. ## References
  147. * [fnparse](http://github.com/joshua-choi/fnparse/) which is awesome for taming parse-m monads in Clojure.
  148. * Thanks to [Greek Diacritics](http://www.tlg.uci.edu/~opoudjis/unicode/gkdiacritics.html) for information about combining diacritics.
  149. * [EPUB](http://en.wikipedia.org/wiki/EPUB) Wikipedia's writup on the EPUB standard is almost all you need.
  150. * [OPF](http://idpf.org/epub/20/spec/OPF_2.0.1_draft.htm) The OPF/NCX standard.
  151. * [The Odyssey](http://www.perseus.tufts.edu/hopper/text?doc=Perseus:text:1999.01.0135) From the Perseus Digital Library, the source for the XML files I have been consuming in this project.