#[cfg(feature = "write")]
mod write;
use crate::ebook::archive::ResourceProvider;
use crate::ebook::archive::errors::ArchiveResult;
use crate::ebook::element::{Attributes, AttributesData, Href, Properties, PropertiesData};
use crate::ebook::manifest::{Manifest, ManifestEntry};
use crate::ebook::resource::consts::mime;
use crate::ebook::resource::{Resource, ResourceKind};
use crate::epub::consts::opf;
use crate::epub::metadata::{EpubMetadataData, EpubRefinements, EpubRefinementsData};
use crate::epub::package::EpubPackageMetaContext;
use crate::input::Many;
use crate::util::iter::IteratorExt;
use crate::util::{Sealed, doc};
use indexmap::IndexMap;
use indexmap::map::Iter as HashMapIter;
use std::collections::HashSet;
use std::fmt::Debug;
use std::io::Write;
#[cfg(feature = "write")]
pub use write::{
DetachedEpubManifestEntry, EpubManifestEntryMut, EpubManifestMut, EpubManifestMutIter,
HrefOptions, IdOptions,
};
const READABLE_CONTENT_MIME: [&str; 2] = [mime::XHTML, mime::HTML];
const SCRIPTS_MIME: [&str; 3] = [mime::JAVASCRIPT, mime::ECMASCRIPT, mime::JAVASCRIPT_TEXT];
#[derive(Debug, PartialEq)]
pub(super) struct EpubManifestData {
pub(super) entries: IndexMap<String, EpubManifestEntryData>,
}
impl EpubManifestData {
pub(super) fn new(entries: IndexMap<String, EpubManifestEntryData>) -> Self {
Self { entries }
}
pub(super) fn empty() -> Self {
Self {
entries: IndexMap::new(),
}
}
pub(super) fn by_id(&self, id: &str) -> Option<(&String, &EpubManifestEntryData)> {
self.entries.get_key_value(id)
}
pub(super) fn by_href(&self, href: &str) -> Option<(&String, &EpubManifestEntryData)> {
self.entries
.iter()
.find(|(_, entry)| entry.href == href || entry.href_raw == href)
}
pub(super) fn iter(&self) -> HashMapIter<'_, String, EpubManifestEntryData> {
self.entries.iter()
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub(super) struct EpubManifestEntryData {
pub(super) href: String,
pub(super) href_raw: String,
pub(super) media_type: String,
pub(super) fallback: Option<String>,
pub(super) media_overlay: Option<String>,
pub(super) properties: PropertiesData,
pub(super) attributes: AttributesData,
pub(super) refinements: EpubRefinementsData,
}
#[derive(Copy, Clone)]
pub(super) struct EpubManifestContext<'ebook> {
resource: ResourceProvider<'ebook>,
meta_ctx: EpubPackageMetaContext<'ebook>,
manifest: Option<&'ebook EpubManifestData>,
}
impl<'ebook> EpubManifestContext<'ebook> {
#[cfg(feature = "write")]
pub(super) const EMPTY: EpubManifestContext<'static> =
EpubManifestContext::new(ResourceProvider::Empty, EpubPackageMetaContext::EMPTY, None);
pub(super) const fn new(
resource: ResourceProvider<'ebook>,
meta_ctx: EpubPackageMetaContext<'ebook>,
manifest: Option<&'ebook EpubManifestData>,
) -> Self {
Self {
resource,
meta_ctx,
manifest,
}
}
pub(super) fn create_entry(
self,
id: &'ebook str,
data: &'ebook EpubManifestEntryData,
) -> EpubManifestEntry<'ebook> {
EpubManifestEntry {
ctx: self,
id,
data,
}
}
pub(super) fn by_id(&self, id: &str) -> Option<EpubManifestEntry<'ebook>> {
self.manifest?
.by_id(id)
.map(|(id, data)| self.create_entry(id, data))
}
pub(super) fn by_href(&self, href: &str) -> Option<EpubManifestEntry<'ebook>> {
self.manifest?
.by_href(href)
.map(|(id, data)| self.create_entry(id, data))
}
}
impl<'ebook> From<EpubManifest<'ebook>> for EpubManifestContext<'ebook> {
fn from(manifest: EpubManifest<'ebook>) -> Self {
manifest.ctx
}
}
#[derive(Copy, Clone)]
pub struct EpubManifest<'ebook> {
ctx: EpubManifestContext<'ebook>,
manifest: &'ebook EpubManifestData,
metadata: &'ebook EpubMetadataData,
}
impl<'ebook> EpubManifest<'ebook> {
pub(super) fn new(
manifest_provider: ResourceProvider<'ebook>,
package_ctx: EpubPackageMetaContext<'ebook>,
manifest: &'ebook EpubManifestData,
metadata: &'ebook EpubMetadataData,
) -> Self {
Self {
ctx: EpubManifestContext::new(manifest_provider, package_ctx, Some(manifest)),
manifest,
metadata,
}
}
pub fn by_id(&self, id: &str) -> Option<EpubManifestEntry<'ebook>> {
self.manifest
.by_id(id)
.map(|(id, data)| self.ctx.create_entry(id, data))
}
pub fn by_href(&self, href: &str) -> Option<EpubManifestEntry<'ebook>> {
self.manifest
.by_href(href)
.map(|(id, data)| self.ctx.create_entry(id, data))
}
pub fn by_property(
&self,
property: &'ebook str,
) -> impl Iterator<Item = EpubManifestEntry<'ebook>> + 'ebook {
let ctx = self.ctx;
self.manifest
.iter()
.filter(|(_, data)| data.properties.has_property(property))
.map(move |(id, data)| ctx.create_entry(id, data))
}
#[doc = doc::inherent!(Manifest, len)]
pub fn len(&self) -> usize {
self.manifest.entries.len()
}
#[doc = doc::inherent!(Manifest, is_empty)]
pub fn is_empty(&self) -> bool {
Manifest::is_empty(self)
}
#[doc = doc::inherent!(Manifest, iter)]
pub fn iter(&self) -> EpubManifestIter<'ebook> {
EpubManifestIter {
ctx: self.ctx,
iter: self.manifest.iter(),
}
}
#[doc = doc::inherent!(Manifest, cover_image)]
pub fn cover_image(&self) -> Option<EpubManifestEntry<'ebook>> {
self.by_property(opf::COVER_IMAGE).next().or_else(|| {
self.metadata
.epub2_cover_image_id()
.and_then(|id| self.by_id(id))
})
}
#[doc = doc::inherent!(Manifest, images)]
pub fn images(&self) -> impl Iterator<Item = EpubManifestEntry<'ebook>> + 'ebook {
self.by_kind(ResourceKind::IMAGE)
}
#[doc = doc::inherent!(Manifest, scripts)]
pub fn scripts(&self) -> impl Iterator<Item = EpubManifestEntry<'ebook>> + 'ebook {
self.by_kind(SCRIPTS_MIME)
}
#[doc = doc::inherent!(Manifest, styles)]
pub fn styles(&self) -> impl Iterator<Item = EpubManifestEntry<'ebook>> + 'ebook {
self.by_kind(mime::CSS)
}
#[doc = doc::inherent!(Manifest, fonts)]
pub fn fonts(&self) -> impl Iterator<Item = EpubManifestEntry<'ebook>> + 'ebook {
self.iter()
.filter(|entry| entry.kind().is_font())
}
#[doc = doc::inherent!(Manifest, audio)]
pub fn audio(&self) -> impl Iterator<Item = EpubManifestEntry<'ebook>> + 'ebook {
self.by_kind(ResourceKind::AUDIO)
}
#[doc = doc::inherent!(Manifest, video)]
pub fn video(&self) -> impl Iterator<Item = EpubManifestEntry<'ebook>> + 'ebook {
self.by_kind(ResourceKind::VIDEO)
}
#[doc = doc::inherent!(Manifest, readable_content)]
pub fn readable_content(&self) -> impl Iterator<Item = EpubManifestEntry<'ebook>> + 'ebook {
self.by_kind(READABLE_CONTENT_MIME)
}
#[doc = doc::inherent!(Manifest, by_kind)]
pub fn by_kind(
&self,
kind: impl Many<ResourceKind<'ebook>>,
) -> impl Iterator<Item = EpubManifestEntry<'ebook>> + 'ebook {
let targets = kind.iter_many().repeatable();
let ctx = self.ctx;
self.manifest
.iter()
.filter(move |(_, data)| {
let kind = ResourceKind::from(data.media_type.as_str());
targets.repeat_once().any(|target| {
if target.is_unspecified() {
target.maintype().eq_ignore_ascii_case(kind.maintype())
} else {
target.as_str().eq_ignore_ascii_case(kind.as_str())
}
})
})
.map(move |(id, data)| ctx.create_entry(id, data))
}
}
impl Sealed for EpubManifest<'_> {}
#[allow(refining_impl_trait)]
impl<'ebook> Manifest<'ebook> for EpubManifest<'ebook> {
fn len(&self) -> usize {
self.len()
}
fn iter(&self) -> EpubManifestIter<'ebook> {
self.iter()
}
fn cover_image(&self) -> Option<EpubManifestEntry<'ebook>> {
self.cover_image()
}
fn images(&self) -> impl Iterator<Item = EpubManifestEntry<'ebook>> + 'ebook {
self.images()
}
fn scripts(&self) -> impl Iterator<Item = EpubManifestEntry<'ebook>> + 'ebook {
self.scripts()
}
fn styles(&self) -> impl Iterator<Item = EpubManifestEntry<'ebook>> + 'ebook {
self.styles()
}
fn fonts(&self) -> impl Iterator<Item = EpubManifestEntry<'ebook>> + 'ebook {
self.fonts()
}
fn audio(&self) -> impl Iterator<Item = EpubManifestEntry<'ebook>> + 'ebook {
self.audio()
}
fn video(&self) -> impl Iterator<Item = EpubManifestEntry<'ebook>> + 'ebook {
self.video()
}
fn readable_content(&self) -> impl Iterator<Item = EpubManifestEntry<'ebook>> + 'ebook {
self.readable_content()
}
fn by_kind(
&self,
kind: impl Many<ResourceKind<'ebook>>,
) -> impl Iterator<Item = EpubManifestEntry<'ebook>> + 'ebook {
self.by_kind(kind)
}
}
impl Debug for EpubManifest<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EpubManifest")
.field("data", self.manifest)
.finish_non_exhaustive()
}
}
impl PartialEq for EpubManifest<'_> {
fn eq(&self, other: &Self) -> bool {
self.manifest == other.manifest
}
}
impl<'ebook> IntoIterator for &EpubManifest<'ebook> {
type Item = EpubManifestEntry<'ebook>;
type IntoIter = EpubManifestIter<'ebook>;
fn into_iter(self) -> EpubManifestIter<'ebook> {
self.iter()
}
}
impl<'ebook> IntoIterator for EpubManifest<'ebook> {
type Item = EpubManifestEntry<'ebook>;
type IntoIter = EpubManifestIter<'ebook>;
fn into_iter(self) -> EpubManifestIter<'ebook> {
self.iter()
}
}
pub struct EpubManifestIter<'ebook> {
ctx: EpubManifestContext<'ebook>,
iter: HashMapIter<'ebook, String, EpubManifestEntryData>,
}
impl<'ebook> Iterator for EpubManifestIter<'ebook> {
type Item = EpubManifestEntry<'ebook>;
fn next(&mut self) -> Option<Self::Item> {
self.iter
.next()
.map(|(id, data)| self.ctx.create_entry(id, data))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
#[derive(Copy, Clone)]
pub struct EpubManifestEntry<'ebook> {
ctx: EpubManifestContext<'ebook>,
id: &'ebook str,
data: &'ebook EpubManifestEntryData,
}
impl<'ebook> EpubManifestEntry<'ebook> {
pub fn id(&self) -> &'ebook str {
self.id
}
pub fn href(&self) -> Href<'ebook> {
Href::new(&self.data.href)
}
pub fn href_raw(&self) -> Href<'ebook> {
Href::new(&self.data.href_raw)
}
pub fn media_type(&self) -> &'ebook str {
&self.data.media_type
}
pub fn media_overlay(&self) -> Option<Self> {
self.data
.media_overlay
.as_deref()
.and_then(|media_overlay| self.ctx.by_id(media_overlay))
}
pub fn fallback(&self) -> Option<Self> {
self.data
.fallback
.as_deref()
.and_then(|fallback| self.ctx.by_id(fallback))
.filter(|entry| !std::ptr::eq(self.data, entry.data))
}
pub fn fallbacks(&self) -> impl Iterator<Item = Self> + 'ebook {
let mut cycle = HashSet::new();
cycle.insert(std::ptr::from_ref(self.data));
std::iter::successors(self.fallback(), move |entry| {
entry.fallback().filter(|entry| cycle.insert(entry.data))
})
}
pub fn properties(&self) -> &'ebook Properties {
&self.data.properties
}
pub fn attributes(&self) -> &'ebook Attributes {
&self.data.attributes
}
pub fn refinements(&self) -> EpubRefinements<'ebook> {
self.ctx
.meta_ctx
.create_refinements(Some(self.id), &self.data.refinements)
}
#[doc = doc::inherent!(ManifestEntry, resource)]
pub fn resource(&self) -> Resource<'ebook> {
Resource::new(self.media_type(), self.data.href.as_str())
}
#[doc = doc::inherent!(ManifestEntry, kind)]
pub fn kind(&self) -> ResourceKind<'ebook> {
self.media_type().into()
}
#[doc = doc::inherent!(ManifestEntry, copy_bytes)]
pub fn copy_bytes(&self, writer: &mut impl Write) -> ArchiveResult<u64> {
self.ctx.resource.copy_bytes(&self.resource(), writer)
}
#[doc = doc::inherent!(ManifestEntry, read_str)]
pub fn read_str(&self) -> ArchiveResult<String> {
ManifestEntry::read_str(self)
}
#[doc = doc::inherent!(ManifestEntry, read_bytes)]
pub fn read_bytes(&self) -> ArchiveResult<Vec<u8>> {
ManifestEntry::read_bytes(self)
}
}
impl Sealed for EpubManifestEntry<'_> {}
impl<'ebook> ManifestEntry<'ebook> for EpubManifestEntry<'ebook> {
fn resource(&self) -> Resource<'ebook> {
self.resource()
}
fn kind(&self) -> ResourceKind<'ebook> {
self.kind()
}
fn copy_bytes(&self, writer: &mut impl Write) -> ArchiveResult<u64> {
self.copy_bytes(writer)
}
}
impl<'ebook> From<EpubManifestEntry<'ebook>> for Resource<'ebook> {
fn from(entry: EpubManifestEntry<'ebook>) -> Self {
entry.resource()
}
}
impl Debug for EpubManifestEntry<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EpubManifestEntry")
.field("id", &self.id)
.field("data", self.data)
.finish_non_exhaustive()
}
}
impl PartialEq for EpubManifestEntry<'_> {
fn eq(&self, other: &Self) -> bool {
self.id == other.id && self.data == other.data
}
}