Coverage for src/epublib/ncx/reset.py: 100%

49 statements  

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

1from pathlib import Path 

2 

3from bs4.element import NamespacedAttribute 

4 

5from epublib.exceptions import EPUBError 

6from epublib.ncx.resource import NCXFile 

7from epublib.package.metadata import BookMetadata, ValuedMetadataItem 

8from epublib.types import BookProtocol 

9 

10ncx_template = """<?xml version="1.0" encoding="UTF-8"?> 

11<ncx version="2005-1" {lang_attr} xmlns="http://www.daisy.org/z3986/2005/ncx/"> 

12<head></head> 

13<docTitle><text>{title}</text></docTitle> 

14<navMap></navMap> 

15</ncx 

16""" 

17 

18 

19def get_minimal_ncx_content(title: str, lang: str | None) -> bytes: 

20 """ 

21 Get a minimal NCX file content with the given title and language. 

22 Caution: the minimality of this template is in regard to the parsing 

23 available in this library. To get a minimal valid NCX file, consider 

24 using `EPUB.generate_ncx` instead. 

25 """ 

26 if lang: 

27 lang_attr = f'xml:lang="{lang}"' 

28 else: 

29 lang_attr = "" 

30 return ncx_template.format(title=title, lang_attr=lang_attr).encode() 

31 

32 

33def generate_ncx(book: BookProtocol, filename: str | Path | None = None) -> NCXFile: 

34 if filename is None: 

35 filename = book.base_dir / "toc.ncx" 

36 

37 if not book.metadata.title: 

38 raise EPUBError("Can't generate NCX without book title in metadata") 

39 

40 if book.ncx is not None: 

41 raise EPUBError( 

42 "Can't generate NCX as it already exists. Try " 

43 f"{book.__class__.__name__}.reset_ncx() instead" 

44 ) 

45 

46 ncx = NCXFile( 

47 get_minimal_ncx_content( 

48 book.metadata.title, 

49 book.metadata.language, 

50 ), 

51 filename, 

52 ) 

53 

54 ncx = reset_ncx(book, ncx) 

55 book.resources.add(ncx) 

56 book.spine.tag["toc"] = book.manifest[ncx.filename].id 

57 return ncx 

58 

59 

60def reset_author(ncx: NCXFile, metadata: BookMetadata) -> None: 

61 creator_item = metadata.get("creator") 

62 creator = ( 

63 creator_item.value if isinstance(creator_item, ValuedMetadataItem) else None 

64 ) 

65 

66 for author in ncx.authors: 

67 if author.text == creator: 

68 continue 

69 __ = ncx.remove_author(author) 

70 

71 if creator and not ncx.get_author(creator): 

72 __ = ncx.add_author(creator) 

73 

74 

75def reset_ncx(book: BookProtocol, ncx: NCXFile | None = None) -> NCXFile: 

76 if not book.metadata.title: 

77 raise EPUBError("Can't reset NCX without book title in metadata") 

78 

79 if ncx is None: 

80 ncx = book.ncx 

81 

82 if ncx is None: 

83 return generate_ncx(book) 

84 

85 ncx.title.text = book.metadata.title 

86 reset_author(ncx, book.metadata) 

87 

88 if book.metadata.language: 

89 ncx.soup.ncx[NamespacedAttribute("xml", "lang")] = book.metadata.language 

90 

91 __ = ncx.sync_toc(book.nav) 

92 if book.nav.page_list: 

93 __ = ncx.sync_page_list(book.nav) 

94 __ = ncx.sync_head(book.metadata) 

95 

96 return ncx