Het EPUB-standaard (dev blog #2)

Welkom in het derde deel van de series over de ontwikkeling van mijn ebook reader app! In deze post ga ik het EPUB-formaat uitleggen, dat nodig is voor het maken van de parser. Laten we er meteen induiken: wat is het precies?

Introductie

EPUB is hét ebook-standaard dat wereldwijd wordt erkend en gebruikt, al decennialang. De laatste revisie van zijn specificatie is gemaakt op 3 november 2023 door W3c, World Wide Web Consortium, de organisatie die webstandaarden ontwerpt. De huidige versie is 3.3.

Om goed te weten hoe ebooks in elkaar steken, is het dus verstandig om de specificatie zorgvuldig door te nemen. In de introductie op de site lezen we dat een ebook in feite een ZIP-archief is met allerlei bestanden, die elk weer hun doel hebben. Dat heet de EPUB-container.

Het container.xml-bestand en het metadata-element

Verder lezen we dat een ebook een verplicht container.xml-bestand in de META-INF-folder heeft staan. In dat XML-bestand staan verwijzingen naar de package-bestanden die gebruikt worden in het ebook. Zo staat er meestal een OPF-bestand aangegeven in het rootfiles element (Open Packaging Format). Dat is het package-document waarin diverse belangrijke gegevens staan, zoals de beschrijving van het boek en de inhoudsopgave.

Het eerste element in het package-document is het metadata-element. Het metadata-element bevat de gegevens van het ebook, zoals de naam, publicatiedatum, auteur, enzovoort. Een ebook is verplicht om de dc:title, dc:identifier, dc:language-elementen en dcterms:modified-property te hebben, anders is het niet goed opgemaakt. Dit is een voorbeeld van een metadata-element:

<package … unique-identifier="pub-id">
    …
    <metadata …>
       <dc:identifier
           id="pub-id">
          urn:uuid:A1B0D67E-2E81-4DF5-9E67-A64CBE366809
       </dc:identifier>
       <dc:title>
          Norwegian Wood
       </dc:title>
       <dc:language>
          en
       </dc:language>
       <meta
           property="dcterms:modified">
          2011-01-01T12:00:00Z
       </meta>
    </metadata>
    …
</package>

Het manifest, spine en nav-element

Verder bevat het document een verplicht manifest-element. Dit element bevat verwijzingen naar de resources die gebruikt worden in het weergeven van de inhoud. In het manifest-bestand zitten meestal meerdere item-elementen met de verplichte properties href en id. De href moet een absoluut of relatief pad zijn dat niet begint met een URL-schema, dus bijvoorbeeld geen file:/. Het id kan van alles zijn, zolang het maar uniek is. Het manifest ziet er bijvoorbeeld zo uit:

<package …>
   …
   <manifest>
      <item
          id="nav"
          href="nav.xhtml"
          properties="nav"
          media-type="application/xhtml+xml"/>
      <item
          id="intro"
          href="intro.xhtml"
          media-type="application/xhtml+xml"/>
      <item
          id="c1"
          href="chap1.xhtml"
          media-type="application/xhtml+xml"/>
      <item
          id="c1-answerkey"
          href="chap1-answerkey.xhtml"
          media-type="application/xhtml+xml"/>
      <item
          id="c2"
          href="chap2.xhtml"
          media-type="application/xhtml+xml"/>
      <item
          id="c2-answerkey"
          href="chap2-answerkey.xhtml"
          media-type="application/xhtml+xml"/>
      <item
          id="c3"
          href="chap3.xhtml"
          media-type="application/xhtml+xml"/>
      <item
          id="c3-answerkey"
          href="chap3-answerkey.xhtml"
          media-type="application/xhtml+xml"/>
      <item
          id="notes"
          href="notes.xhtml"
          media-type="application/xhtml+xml"/>
      <item
          id="cover"
          href="./images/cover.svg"
          properties="cover-image"
          media-type="image/svg+xml"/>
      <item
          id="f1"
          href="./images/fig1.jpg"
          media-type="image/jpeg"/>
      <item
          id="f2"
          href="./images/fig2.jpg"
          media-type="image/jpeg"/>
      <item
          id="css"
          href="./style/book.css"
          media-type="text/css"/>
   </manifest>
   …
</package>

Het spine-element regelt de volgorde van de inhoud. Dat element bevat vaak meerdere itemref-elementen. Hieronder zie je een voorbeeld van een spine-element:

<spine
    page-progression-direction="ltr">
   <itemref
       idref="intro"/>
   <itemref
       idref="c1"/>
   <itemref
       idref="c1-answerkey"
       linear="no"/>
   <itemref
       idref="c2"/>
   <itemref
       idref="c2-answerkey"
       linear="no"/>
   <itemref
       idref="c3"/>
   <itemref
       idref="c3-answerkey"
       linear="no"/>
   <itemref
       idref="notes"
       linear="no"/>
</spine>

Zoals je ziet bevat het spine-element een rij met itemref-elementen die met hun idref-property verwijzen naar het item-element in het manifest. Dit element is dus erg belangrijk voor het bepalen van de inhoud van het ebook.

Uiteindelijk wordt de inhoudsopgave in het nav-element bepaald. Dit element bevat een genummerde lijst met diverse <a epub:type="toc" href="#toc">-elementen die verwijzen naar de inhoud in de EPUB-container. De tekst die er in staat is de naam van de pagina. In EPUB 2 wordt dit geregeld in het navMap-element, waardoor het verstandig is om ook dat te ondersteunen zodat je een fallback hebt. Dit is hoe een nav-element er bijvoorbeeld uit ziet:

<nav epub:type="landmarks">
   <h2>Guide</h2>
   <ol>
       <li>
          <a epub:type="toc"
             href="#toc">
            Table of Contents
          </a>
       </li>
       <li>
          <a epub:type="loi"
             href="content.html#loi">
            List of Illustrations
          </a>
       </li>
       <li>
          <a epub:type="bodymatter"
             href="content.html#bodymatter">
            Start of Content
          </a>
       </li>
   </ol>
</nav>

Kortom, er komt dus een hoop kijken bij het parsen van een ebook. Je hebt het package-document met de metadata, waarin belangrijke gegevens staan over het boek, maar ook het manifest-element met verwijzingen naar de inhoud, het spine-element voor de volgorde daarvan, en het toc.ncx-bestand dat de inhoudsopgave bepaald. Verder heb je natuurlijk de HTML-bestanden die de inhoud representeren, maar dat spreekt voor zichzelf.

Dit was het voor nu. Bedankt voor het lezen en hopelijk zie ik je weer bij een volgend artikel!