#[cfg(feature = "write")]
mod write;
use crate::ebook::element::{Attributes, AttributesData, Href};
use crate::ebook::resource::Resource;
use crate::ebook::toc::{Toc, TocEntry, TocEntryKind};
use crate::epub::EpubVersion;
use crate::epub::manifest::{EpubManifestContext, EpubManifestEntry};
use crate::util::{Sealed, doc};
use indexmap::IndexMap;
use indexmap::map::Iter as HashMapIter;
use std::fmt::Debug;
use std::slice::Iter as SliceIter;
#[cfg(feature = "write")]
pub use write::{
DetachedEpubTocEntry, EpubTocEntryIterMut, EpubTocEntryMut, EpubTocIterMut, EpubTocMut,
};
pub(super) type TocGroups = IndexMap<EpubTocKey, EpubTocEntryData>;
#[derive(Debug, Hash, PartialEq, Eq)]
pub(super) struct EpubTocKey {
pub(super) kind: String,
pub(super) version: EpubVersion,
}
impl EpubTocKey {
pub(super) fn new(kind: String, version: EpubVersion) -> Self {
Self { kind, version }
}
#[cfg(feature = "write")]
pub(super) fn kind(&self) -> TocEntryKind<'_> {
TocEntryKind::from(&self.kind)
}
}
impl indexmap::Equivalent<EpubTocKey> for (&str, EpubVersion) {
fn equivalent(&self, key: &EpubTocKey) -> bool {
self.0 == key.kind && self.1 == key.version
}
}
#[derive(Debug, PartialEq)]
pub(super) struct EpubTocData {
pub(super) preferred_version: EpubVersion,
pub(super) entries: TocGroups,
}
impl EpubTocData {
pub(super) fn new(entries: TocGroups) -> Self {
Self {
preferred_version: EpubVersion::EPUB3,
entries,
}
}
pub(super) fn empty() -> Self {
Self::new(IndexMap::new())
}
pub(super) fn extend(&mut self, data: Self) {
self.entries.extend(data.entries);
}
pub(super) fn get_preferred_version(&self, kind: TocEntryKind) -> EpubVersion {
match kind {
TocEntryKind::Landmarks | TocEntryKind::PageList | TocEntryKind::Toc => {
self.preferred_version
}
_ => EpubVersion::EPUB3,
}
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub(super) struct EpubTocEntryData {
pub(super) id: Option<String>,
pub(super) label: String,
pub(super) kind: Option<String>,
pub(super) href: Option<String>,
pub(super) href_raw: Option<String>,
pub(super) attributes: AttributesData,
pub(super) children: Vec<Self>,
}
#[derive(Copy, Clone)]
pub(super) struct EpubTocContext<'ebook> {
manifest_ctx: EpubManifestContext<'ebook>,
}
impl<'ebook> EpubTocContext<'ebook> {
pub(super) fn new(manifest_ctx: EpubManifestContext<'ebook>) -> Self {
Self { manifest_ctx }
}
pub(super) fn create_root(
self,
version: EpubVersion,
data: &'ebook EpubTocEntryData,
) -> EpubTocEntry<'ebook> {
self.create_entry(version, data, 0)
}
pub(super) fn create_entry(
self,
version: EpubVersion,
data: &'ebook EpubTocEntryData,
depth: usize,
) -> EpubTocEntry<'ebook> {
EpubTocEntry {
ctx: self,
version,
data,
depth,
}
}
}
#[derive(Copy, Clone)]
pub struct EpubToc<'ebook> {
ctx: EpubTocContext<'ebook>,
toc: &'ebook EpubTocData,
}
impl<'ebook> EpubToc<'ebook> {
pub(super) fn new(manifest_ctx: EpubManifestContext<'ebook>, toc: &'ebook EpubTocData) -> Self {
Self {
ctx: EpubTocContext::new(manifest_ctx),
toc,
}
}
fn by_toc_key(&self, kind: &str, version: EpubVersion) -> Option<EpubTocEntry<'ebook>> {
self.toc
.entries
.get(&(kind, version))
.map(|data| self.ctx.create_root(version, data))
}
pub fn page_list(&self) -> Option<EpubTocEntry<'ebook>> {
self.by_kind(TocEntryKind::PageList)
}
pub fn landmarks(&self) -> Option<EpubTocEntry<'ebook>> {
self.by_kind(TocEntryKind::Landmarks)
}
pub fn by_kind_version(
&self,
kind: impl Into<TocEntryKind<'ebook>>,
version: EpubVersion,
) -> Option<EpubTocEntry<'ebook>> {
let kind = kind.into();
self.by_toc_key(kind.as_str(), version.as_major())
}
#[doc = doc::inherent!(Toc, contents)]
pub fn contents(&self) -> Option<EpubTocEntry<'ebook>> {
self.by_kind(TocEntryKind::Toc)
}
#[doc = doc::inherent!(Toc, by_kind)]
pub fn by_kind(&self, kind: impl Into<TocEntryKind<'ebook>>) -> Option<EpubTocEntry<'ebook>> {
let kind = kind.into();
let preferred_version = self.toc.get_preferred_version(kind);
let attempts = std::iter::once(preferred_version)
.chain(EpubVersion::VERSIONS);
for version in attempts {
if let Some(root) = self.by_toc_key(kind.as_str(), version) {
return Some(root);
}
}
None
}
#[doc = doc::inherent!(Toc, iter)]
pub fn iter(&self) -> EpubTocIter<'ebook> {
self.into_iter()
}
}
impl Sealed for EpubToc<'_> {}
#[allow(refining_impl_trait)]
impl<'ebook> Toc<'ebook> for EpubToc<'ebook> {
fn contents(&self) -> Option<EpubTocEntry<'ebook>> {
self.contents()
}
fn by_kind(&self, kind: impl Into<TocEntryKind<'ebook>>) -> Option<EpubTocEntry<'ebook>> {
self.by_kind(kind)
}
fn iter(&self) -> EpubTocIter<'ebook> {
self.iter()
}
}
impl Debug for EpubToc<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EpubToc")
.field("data", self.toc)
.finish_non_exhaustive()
}
}
impl PartialEq for EpubToc<'_> {
fn eq(&self, other: &Self) -> bool {
self.toc == other.toc
}
}
impl<'ebook> IntoIterator for &EpubToc<'ebook> {
type Item = EpubTocEntry<'ebook>;
type IntoIter = EpubTocIter<'ebook>;
fn into_iter(self) -> Self::IntoIter {
EpubTocIter {
ctx: self.ctx,
iter: self.toc.entries.iter(),
}
}
}
impl<'ebook> IntoIterator for EpubToc<'ebook> {
type Item = EpubTocEntry<'ebook>;
type IntoIter = EpubTocIter<'ebook>;
fn into_iter(self) -> Self::IntoIter {
(&self).into_iter()
}
}
pub struct EpubTocIter<'ebook> {
ctx: EpubTocContext<'ebook>,
iter: HashMapIter<'ebook, EpubTocKey, EpubTocEntryData>,
}
impl<'ebook> Iterator for EpubTocIter<'ebook> {
type Item = EpubTocEntry<'ebook>;
fn next(&mut self) -> Option<Self::Item> {
self.iter
.next()
.map(move |(key, data)| self.ctx.create_root(key.version, data))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
#[derive(Copy, Clone)]
pub struct EpubTocEntry<'ebook> {
ctx: EpubTocContext<'ebook>,
version: EpubVersion,
data: &'ebook EpubTocEntryData,
depth: usize,
}
impl<'ebook> EpubTocEntry<'ebook> {
pub fn version(&self) -> EpubVersion {
self.version
}
pub fn id(&self) -> Option<&'ebook str> {
self.data.id.as_deref()
}
pub fn kind_raw(&self) -> Option<&'ebook str> {
self.data.kind.as_deref()
}
pub fn href(&self) -> Option<Href<'ebook>> {
self.data.href.as_deref().map(Href::new)
}
pub fn href_raw(&self) -> Option<Href<'ebook>> {
self.data.href_raw.as_deref().map(Href::new)
}
pub fn attributes(&self) -> &'ebook Attributes {
&self.data.attributes
}
#[doc = doc::inherent!(TocEntry, depth)]
pub fn depth(&self) -> usize {
self.depth
}
#[doc = doc::inherent!(TocEntry, label)]
pub fn label(&self) -> &'ebook str {
&self.data.label
}
#[doc = doc::inherent!(TocEntry, kind)]
pub fn kind(&self) -> TocEntryKind<'ebook> {
self.data
.kind
.as_deref()
.map(TocEntryKind::from)
.unwrap_or_default()
}
#[doc = doc::inherent!(TocEntry, manifest_entry)]
pub fn manifest_entry(&self) -> Option<EpubManifestEntry<'ebook>> {
self.href()
.and_then(|href| self.ctx.manifest_ctx.by_href(href.path().as_str()))
}
#[doc = doc::inherent!(TocEntry, resource)]
pub fn resource(&self) -> Option<Resource<'ebook>> {
TocEntry::resource(self)
}
#[doc = doc::inherent!(TocEntry, get)]
pub fn get(&self, index: usize) -> Option<Self> {
self.data
.children
.get(index)
.map(|data| self.ctx.create_entry(self.version, data, self.depth + 1))
}
#[doc = doc::inherent!(TocEntry, iter)]
pub fn iter(&self) -> EpubTocEntryIter<'ebook> {
EpubTocEntryIter {
ctx: self.ctx,
version: self.version,
iter: self.data.children.iter(),
next_depth: self.depth + 1,
}
}
#[doc = doc::inherent!(TocEntry, flatten)]
pub fn flatten(&self) -> impl Iterator<Item = Self> + 'ebook {
struct FlatEpubTocEntryIterator<'ebook> {
ctx: EpubTocContext<'ebook>,
version: EpubVersion,
stack: Vec<(usize, &'ebook EpubTocEntryData)>, }
impl<'ebook> Iterator for FlatEpubTocEntryIterator<'ebook> {
type Item = EpubTocEntry<'ebook>;
fn next(&mut self) -> Option<Self::Item> {
let (depth, data) = self.stack.pop()?;
self.stack
.extend(data.children.iter().rev().map(|data| (depth + 1, data)));
Some(self.ctx.create_entry(self.version, data, depth))
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.stack.len(), None)
}
}
FlatEpubTocEntryIterator {
ctx: self.ctx,
version: self.version,
stack: self
.data
.children
.iter()
.rev()
.map(|data| (self.depth + 1, data))
.collect(),
}
}
#[doc = doc::inherent!(TocEntry, len)]
pub fn len(&self) -> usize {
self.data.children.len()
}
#[doc = doc::inherent!(TocEntry, is_empty)]
pub fn is_empty(&self) -> bool {
TocEntry::is_empty(self)
}
#[doc = doc::inherent!(TocEntry, is_root)]
pub fn is_root(&self) -> bool {
TocEntry::is_root(self)
}
#[doc = doc::inherent!(TocEntry, max_depth)]
pub fn max_depth(&self) -> usize {
TocEntry::max_depth(self)
}
#[doc = doc::inherent!(TocEntry, total_len)]
pub fn total_len(&self) -> usize {
TocEntry::total_len(self)
}
}
impl Sealed for EpubTocEntry<'_> {}
#[allow(refining_impl_trait)]
impl<'ebook> TocEntry<'ebook> for EpubTocEntry<'ebook> {
fn depth(&self) -> usize {
self.depth()
}
fn label(&self) -> &'ebook str {
self.label()
}
fn kind(&self) -> TocEntryKind<'ebook> {
self.kind()
}
fn manifest_entry(&self) -> Option<EpubManifestEntry<'ebook>> {
self.manifest_entry()
}
fn get(&self, index: usize) -> Option<Self> {
self.get(index)
}
fn iter(&self) -> EpubTocEntryIter<'ebook> {
self.iter()
}
fn flatten(&self) -> impl Iterator<Item = Self> + 'ebook {
self.flatten()
}
fn len(&self) -> usize {
self.len()
}
}
impl Debug for EpubTocEntry<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EpubTocEntry")
.field("data", self.data)
.finish_non_exhaustive()
}
}
impl<'ebook> IntoIterator for &EpubTocEntry<'ebook> {
type Item = EpubTocEntry<'ebook>;
type IntoIter = EpubTocEntryIter<'ebook>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'ebook> IntoIterator for EpubTocEntry<'ebook> {
type Item = Self;
type IntoIter = EpubTocEntryIter<'ebook>;
fn into_iter(self) -> Self::IntoIter {
(&self).into_iter()
}
}
impl PartialEq<Self> for EpubTocEntry<'_> {
fn eq(&self, other: &Self) -> bool {
self.data == other.data
}
}
pub struct EpubTocEntryIter<'ebook> {
ctx: EpubTocContext<'ebook>,
version: EpubVersion,
iter: SliceIter<'ebook, EpubTocEntryData>,
next_depth: usize,
}
impl<'ebook> Iterator for EpubTocEntryIter<'ebook> {
type Item = EpubTocEntry<'ebook>;
fn next(&mut self) -> Option<Self::Item> {
self.iter
.next()
.map(|data| self.ctx.create_entry(self.version, data, self.next_depth))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}