Coverage for src/epublib/package/resource.py: 95%
82 statements
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-06 15:17 -0300
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-06 15:17 -0300
1from pathlib import Path
2from typing import IO, cast, override
3from zipfile import ZipInfo
5import bs4
7from epublib.exceptions import EPUBError
8from epublib.identifier import EPUBId
9from epublib.media_type import MediaType
10from epublib.package.guide import BookGuide
11from epublib.package.manifest import (
12 BookManifest,
13 ManifestItem,
14 detect_manifest_properties,
15)
16from epublib.package.metadata import BookMetadata, ValuedMetadataItem
17from epublib.package.spine import BookSpine
18from epublib.resources import (
19 ContentDocument,
20 PublicationResource,
21 Resource,
22 XMLResource,
23)
24from epublib.soup import PackageDocumentSoup
27class PackageDocument(XMLResource[PackageDocumentSoup]):
28 """The package document of the EPUB file, sometimes known as the 'content.opf' file."""
30 soup_class: type[PackageDocumentSoup] = PackageDocumentSoup
32 def __init__(self, file: IO[bytes] | bytes, info: ZipInfo | str | Path) -> None:
33 super().__init__(file, info)
34 self._manifest: BookManifest | None = None
35 self._metadata: BookMetadata | None = None
36 self._spine: BookSpine | None = None
37 self._guide: BookGuide | None = None
39 @property
40 def manifest(self):
41 if self._manifest is None:
42 self._manifest = BookManifest(
43 self.soup,
44 self.soup.manifest,
45 own_filename=self.filename,
46 )
47 return self._manifest
49 @property
50 def metadata(self):
51 if self._metadata is None:
52 self._metadata = BookMetadata(self.soup, self.soup.metadata)
53 return self._metadata
55 @property
56 def spine(self):
57 if self._spine is None:
58 self._spine = BookSpine(self.soup, self.soup.spine)
59 return self._spine
61 @property
62 def guide(self):
63 if self._guide is None and self.soup.guide:
64 self._guide = BookGuide(
65 self.soup,
66 self.soup.guide,
67 own_filename=self.filename,
68 )
69 return self._guide
71 def remove(self, filename: str):
72 item = self.manifest[filename]
73 spine_item = self.spine.get(item.id)
74 if spine_item:
75 self.spine.remove_item(spine_item)
76 self.manifest.remove_item(item)
77 if item.has_property("cover-image"):
78 metadata_item = self.metadata.get("cover", ValuedMetadataItem)
79 if metadata_item and metadata_item.value == item.id:
80 self.metadata.remove_item(metadata_item)
82 def on_soup_change(self):
83 del self._manifest
84 del self._metadata
85 del self._spine
86 self._manifest = None
87 self._metadata = None
88 self._spine = None
90 @override
91 def on_content_change(self):
92 super().on_content_change()
93 self.on_soup_change()
96def resource_to_manifest_item(
97 resource: Resource,
98 package: PackageDocument,
99 identifier: EPUBId | str | None = None,
100 media_type: MediaType | str | None = None,
101 fallback: str | None = None,
102 media_overlay: str | None = None,
103 is_nav: bool = False,
104 is_cover: bool = False,
105 properties: list[str] | None = None,
106 detect_properties: bool = True,
107):
108 filename = resource.filename
110 if identifier is None:
111 identifier = package.manifest.get_new_id(resource.filename)
112 elif package.manifest.get(identifier) is not None:
113 raise EPUBError(f"Identifier '{identifier}' is already used in the manifest")
115 if media_type is None:
116 media_type = (
117 resource.media_type
118 if isinstance(resource, PublicationResource)
119 else MediaType.from_filename(resource.filename)
120 )
122 if not media_type:
123 raise EPUBError(f"Can't determine media type of file {resource.filename}")
125 if detect_properties or is_nav or is_cover:
126 properties = properties if properties is not None else []
128 if detect_properties and isinstance(resource, ContentDocument):
129 properties += detect_manifest_properties(
130 cast(ContentDocument[bs4.BeautifulSoup], resource).soup
131 )
133 if is_nav:
134 properties.append("nav")
136 if is_cover:
137 properties.append("cover-image")
139 properties = list(set(properties)) if properties else None
141 return ManifestItem(
142 soup=package.soup,
143 filename=filename,
144 id=EPUBId(identifier),
145 media_type=str(media_type),
146 media_overlay=media_overlay,
147 fallback=fallback,
148 properties=properties,
149 own_filename=package.filename,
150 )