Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/imposition/book.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,26 @@

from .exceptions import InvalidEpubError, MissingContainerError


class Book:
"""
Parses and provides access to the contents of an EPUB file.

This class takes the binary content of an EPUB file, parses its
structure, and provides methods to access its table of contents and
spine.
"""

def __init__(self, epub_bytes: bytes) -> None:
"""
Initializes the Book object from a bytes object of the EPUB file.

:param epub_bytes: The binary content of the EPUB file.
:type epub_bytes: bytes
:raises InvalidEpubError: If the file is not a valid ZIP archive or if
the EPUB structure is invalid.
:raises MissingContainerError: If the META-INF/container.xml file is
not found.
"""
epub_file = io.BytesIO(epub_bytes)
try:
Expand Down Expand Up @@ -55,6 +71,13 @@ def __init__(self, epub_bytes: bytes) -> None:
self.toc: List[Dict[str, str]] = self._parse_toc()

def get_toc(self) -> List[Dict[str, str]]:
"""
Returns the table of contents.

:return: A list of dictionaries, where each dictionary represents a
table of contents item with 'title' and 'url' keys.
:rtype: List[Dict[str, str]]
"""
return self.toc

def _parse_toc(self) -> List[Dict[str, str]]:
Expand Down
20 changes: 17 additions & 3 deletions src/imposition/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
class ImpositionError(Exception):
"""Base class for exceptions in this module."""
"""Base class for all exceptions raised by the imposition library."""

pass


class InvalidEpubError(ImpositionError):
"""Exception raised for errors in the EPUB file format."""
"""
Exception raised for errors related to the EPUB file format.

This can include issues with the ZIP structure, XML parsing, or missing
required components in the EPUB container.
"""

pass


class MissingContainerError(InvalidEpubError):
"""Exception raised when META-INF/container.xml is missing."""
"""
Exception raised when the META-INF/container.xml file is missing.

This file is essential for locating the root OPF file of the EPUB.
"""

pass
43 changes: 42 additions & 1 deletion src/imposition/rendition.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,25 @@
if TYPE_CHECKING:
from .book import Book


class Rendition:
"""
Renders an EPUB file into a web-based reader interface.

This class handles the display of the EPUB's table of contents and
chapters, and provides navigation between them.
"""

def __init__(self, book: Book, target_id: str) -> None:
"""
Initializes the Rendition object.

:param book: An initialized Book object.
:type book: Book
:param target_id: The ID of the HTML element where the EPUB content
will be rendered.
:type target_id: str
"""
self.book: Book = book
self.target_id: str = target_id
self.target_element: JsProxy = document.getElementById(self.target_id)
Expand All @@ -23,6 +40,9 @@ def __init__(self, book: Book, target_id: str) -> None:
self.iframe.style.border = 'none'

def display_toc(self) -> None:
"""
Renders the table of contents into the 'toc-container' element.
"""
toc_container: JsProxy = document.getElementById('toc-container')
toc_container.innerHTML = ''
ul: JsProxy = document.createElement('ul')
Expand Down Expand Up @@ -50,9 +70,18 @@ def handler(event: JsProxy) -> None:


def display(self, chapter_url: Optional[str] = None) -> None:
"""
Displays a specific chapter in the rendition iframe.

If no chapter URL is provided, it displays the first chapter in the
spine. It also handles embedding of assets like images.

:param chapter_url: The URL of the chapter to display. Can include an
anchor.
:type chapter_url: Optional[str]
"""
if not self.book.spine:
return

anchor: Optional[str] = None
chapter_href: str
if chapter_url and '#' in chapter_url:
Expand Down Expand Up @@ -126,11 +155,23 @@ def _embed_asset(self, element: ET.Element, attribute: str, chapter_path: str) -
pass

def next_chapter(self, event: Optional[Any] = None) -> None:
"""
Displays the next chapter in the book's spine.

:param event: An optional event object (e.g., from a button click).
:type event: Optional[Any]
"""
if self.current_chapter_index < len(self.book.spine) - 1:
self.current_chapter_index += 1
self.display(self.book.spine[self.current_chapter_index])

def previous_chapter(self, event: Optional[Any] = None) -> None:
"""
Displays the previous chapter in the book's spine.

:param event: An optional event object (e.g., from a button click).
:type event: Optional[Any]
"""
if self.current_chapter_index > 0:
self.current_chapter_index -= 1
self.display(self.book.spine[self.current_chapter_index])