Coverage for src/epublib/nav/resource.py: 100%

74 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2025-10-07 10:22 -0300

1from pathlib import Path 

2from typing import IO, override 

3from zipfile import ZipInfo 

4 

5from epublib.exceptions import EPUBError 

6from epublib.media_type import MediaType 

7from epublib.nav import LandmarksRoot, PageListRoot, TocRoot 

8from epublib.nav.util import LandmarkEntryData, PageBreakData, TOCEntryData 

9from epublib.resources import ContentDocument 

10 

11 

12class NavigationDocument(ContentDocument): 

13 """ 

14 A specialization of the XHTML content document that contains human- and 

15 machine-readable global navigation information. 

16 """ 

17 

18 def __init__( 

19 self, 

20 file: IO[bytes] | bytes, 

21 info: ZipInfo | str | Path, 

22 media_type: MediaType | str, 

23 ) -> None: 

24 super().__init__(file, info, media_type) 

25 self._toc: TocRoot | None = None 

26 self._page_list: PageListRoot | None = None 

27 self._landmarks: LandmarksRoot | None = None 

28 

29 @property 

30 def toc(self) -> TocRoot: 

31 if self._toc is None: 

32 tag = self.soup.select_one('nav[epub|type="toc"]') 

33 if tag: 

34 self._toc = TocRoot.from_tag(self.soup, tag, own_filename=self.filename) 

35 if not self._toc: 

36 raise EPUBError("No TOC found in navigation document") 

37 return self._toc 

38 

39 @property 

40 def page_list(self): 

41 if self._page_list is None: 

42 tag = self.soup.select_one('nav[epub|type="page-list"]') 

43 if tag: 

44 self._page_list = PageListRoot.from_tag( 

45 self.soup, 

46 tag, 

47 own_filename=self.filename, 

48 ) 

49 return self._page_list 

50 

51 @property 

52 def landmarks(self): 

53 if self._landmarks is None: 

54 tag = self.soup.select_one('nav[epub|type="landmarks"]') 

55 if tag: 

56 self._landmarks = LandmarksRoot.from_tag( 

57 self.soup, 

58 tag, 

59 own_filename=self.filename, 

60 ) 

61 return self._landmarks 

62 

63 def reset_page_list(self, pagebreaks: list[PageBreakData]): 

64 if self.page_list is None: 

65 self._page_list = PageListRoot( 

66 self.soup, 

67 own_filename=self.filename, 

68 ) 

69 self._page_list.insert_self_in_soup() 

70 

71 assert self.page_list 

72 self.page_list.reset(pagebreaks) 

73 

74 def reset_toc(self, entries: list[TOCEntryData]): 

75 try: 

76 _ = self.toc 

77 except EPUBError: 

78 self._toc = TocRoot(self.soup, own_filename=self.filename) 

79 self._toc.insert_self_in_soup() 

80 

81 self.toc.reset(entries) 

82 

83 def reset_landmarks(self, entries: list[LandmarkEntryData]): 

84 if self.landmarks is None: 

85 self._landmarks = LandmarksRoot( 

86 self.soup, 

87 own_filename=self.filename, 

88 ) 

89 self._landmarks.insert_self_in_soup() 

90 

91 assert self.landmarks 

92 self.landmarks.reset(entries) 

93 

94 def remove(self, filename: str | Path): 

95 if self.toc: 

96 self.toc.remove_nodes(str(filename)) 

97 if self.landmarks: 

98 self.landmarks.remove_all(filename) 

99 if self.page_list: 

100 self.page_list.remove_all(filename) 

101 

102 def on_soup_change(self): 

103 del self._toc 

104 del self._page_list 

105 del self._landmarks 

106 self._toc = None 

107 self._page_list = None 

108 self._landmarks = None 

109 

110 @override 

111 def on_content_change(self): 

112 super().on_content_change() 

113 self.on_soup_change()