use crate::ebook::element::{Attribute, Attributes};
use crate::ebook::metadata::Version;
use crate::ebook::toc::TocEntryKind;
use crate::epub::manifest::EpubManifestContext;
use crate::epub::metadata::EpubVersion;
use crate::epub::toc::{
EpubToc, EpubTocContext, EpubTocData, EpubTocEntry, EpubTocEntryData, EpubTocKey,
};
use crate::input::{IntoOption, Many};
use crate::util::iter::{IteratorExt, OneOrMany};
use crate::util::uri::{self, UriResolver};
use std::fmt::Debug;
impl<'ebook> EpubTocContext<'ebook> {
const EMPTY: EpubTocContext<'static> = EpubTocContext {
manifest_ctx: EpubManifestContext::EMPTY,
};
fn create(self, data: &'ebook EpubTocData) -> EpubToc<'ebook> {
EpubToc::new(self.manifest_ctx, data)
}
fn create_root_mut(
self,
version: EpubVersion,
href_resolver: Option<UriResolver<'ebook>>,
data: &'ebook mut EpubTocEntryData,
) -> EpubTocEntryMut<'ebook> {
self.create_entry_mut(version, href_resolver, data, 0)
}
fn create_entry_mut(
self,
version: EpubVersion,
href_resolver: Option<UriResolver<'ebook>>,
data: &'ebook mut EpubTocEntryData,
depth: usize,
) -> EpubTocEntryMut<'ebook> {
EpubTocEntryMut::new(self, version, href_resolver, data, depth)
}
}
impl EpubTocData {
pub(crate) fn recursive_retain(
&mut self,
mut f: impl Copy + FnMut(&mut EpubTocEntryData) -> bool,
) {
self.entries.retain(|_, entry| {
let retain = f(entry);
if retain {
entry.recursive_retain_children(f);
}
retain
});
}
}
impl EpubTocEntryData {
pub(crate) fn cascade_toc_href(&mut self, old: &str, new: &str) {
if let Some(href) = &mut self.href
&& uri::path(href) == old
{
let query_and_fragment = href
.find(['?', '#'])
.map(|position| &href[position..])
.unwrap_or_default();
*href = new.to_owned() + query_and_fragment;
self.href_raw = None;
}
for child in &mut self.children {
child.cascade_toc_href(old, new);
}
}
fn recursive_retain_children(&mut self, mut f: impl Copy + FnMut(&mut Self) -> bool) {
self.children.retain_mut(|entry| {
let retain = f(entry);
if retain {
entry.recursive_retain_children(f);
}
retain
});
}
fn resolve_hrefs(&mut self, resolver: UriResolver<'_>) {
if let Some(href_raw) = &self.href_raw {
self.href.replace(resolver.resolve(href_raw));
}
for entry in &mut self.children {
entry.resolve_hrefs(resolver);
}
}
}
impl EpubTocEntry<'_> {
pub fn to_detached(&self) -> DetachedEpubTocEntry {
DetachedEpubTocEntry {
version: self.version,
data: self.data.clone(),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct DetachedEpubTocEntry {
version: EpubVersion,
data: EpubTocEntryData,
}
impl DetachedEpubTocEntry {
fn detached(version: EpubVersion, data: EpubTocEntryData) -> Self {
Self { version, data }
}
pub fn new(label: impl Into<String>) -> Self {
Self {
version: EpubVersion::Unknown(Version(0, 0)),
data: EpubTocEntryData {
label: label.into(),
..EpubTocEntryData::default()
},
}
}
pub fn as_mut(&mut self) -> EpubTocEntryMut<'_> {
EpubTocContext::EMPTY.create_root_mut(self.version, None, &mut self.data)
}
pub fn as_view(&self) -> EpubTocEntry<'_> {
EpubTocContext::EMPTY.create_root(self.version, &self.data)
}
pub fn id(mut self, id: impl IntoOption<String>) -> Self {
self.as_mut().set_id(id);
self
}
pub fn label(mut self, label: impl Into<String>) -> Self {
self.as_mut().set_label(label);
self
}
pub fn kind(mut self, kind: impl IntoOption<String>) -> Self {
self.as_mut().set_kind(kind);
self
}
pub fn href(mut self, raw_href: impl IntoOption<String>) -> Self {
self.as_mut().set_href(raw_href);
self
}
pub fn attribute(mut self, attribute: impl Many<Attribute>) -> Self {
self.as_mut().attributes_mut().extend(attribute.iter_many());
self
}
pub fn children(mut self, child: impl Many<Self>) -> Self {
self.as_mut().push(child);
self
}
}
pub struct EpubTocMut<'ebook> {
ctx: EpubTocContext<'ebook>,
href_resolver: UriResolver<'ebook>,
toc: &'ebook mut EpubTocData,
}
impl<'ebook> EpubTocMut<'ebook> {
pub(in crate::epub) fn new(
ctx: EpubTocContext<'ebook>,
href_resolver: UriResolver<'ebook>,
toc: &'ebook mut EpubTocData,
) -> Self {
Self {
ctx,
href_resolver,
toc,
}
}
fn by_toc_key(&mut self, kind: &str, version: EpubVersion) -> Option<EpubTocEntryMut<'_>> {
self.toc.entries.get_mut(&(kind, version)).map(|data| {
self.ctx
.create_root_mut(version, Some(self.href_resolver), data)
})
}
fn remove_by_toc_key<'a>(
&mut self,
kind: impl Into<TocEntryKind<'a>>,
version: EpubVersion,
) -> Option<DetachedEpubTocEntry> {
let kind = kind.into();
self.toc
.entries
.shift_remove(&(kind.as_str(), version))
.map(|data| DetachedEpubTocEntry::detached(version, data))
}
pub fn contents_mut(&mut self) -> Option<EpubTocEntryMut<'_>> {
self.by_kind_mut(TocEntryKind::Toc)
}
pub fn landmarks_mut(&mut self) -> Option<EpubTocEntryMut<'_>> {
self.by_kind_mut(TocEntryKind::Landmarks)
}
pub fn page_list_mut(&mut self) -> Option<EpubTocEntryMut<'_>> {
self.by_kind_mut(TocEntryKind::PageList)
}
pub fn by_kind_version_mut<'a>(
&mut self,
kind: impl Into<TocEntryKind<'a>>,
version: EpubVersion,
) -> Option<EpubTocEntryMut<'_>> {
let kind = kind.into();
self.by_toc_key(kind.as_str(), version.as_major())
}
pub fn by_kind_mut<'a>(
&mut self,
kind: impl Into<TocEntryKind<'a>>,
) -> Option<EpubTocEntryMut<'_>> {
let kind = kind.into();
let preferred_version = self.toc.get_preferred_version(kind);
let attempts = std::iter::once(preferred_version)
.chain(EpubVersion::VERSIONS);
let mut key = (kind.as_str(), preferred_version);
for version in attempts {
key.1 = version;
if self.toc.entries.contains_key(&key) {
break;
}
}
self.by_toc_key(key.0, key.1)
}
pub fn insert_root<'a>(
&mut self,
kind: impl Into<TocEntryKind<'a>>,
version: impl Into<EpubVersion>,
detached: DetachedEpubTocEntry,
) -> Option<DetachedEpubTocEntry> {
let kind = kind.into().to_string();
let version = version.into().as_major();
let key = EpubTocKey::new(kind.clone(), version);
let mut root = detached.data;
root.kind = Some(kind);
root.resolve_hrefs(self.href_resolver);
self.toc
.entries
.insert(key, root)
.map(|root| DetachedEpubTocEntry::detached(version, root))
}
pub fn iter_mut(&mut self) -> EpubTocIterMut<'_> {
EpubTocIterMut {
ctx: self.ctx,
href_resolver: self.href_resolver,
iter: self.toc.entries.iter_mut(),
}
}
pub fn remove_by_kind_version<'a>(
&mut self,
kind: impl Into<TocEntryKind<'a>>,
version: EpubVersion,
) -> Option<DetachedEpubTocEntry> {
self.remove_by_toc_key(kind.into(), version)
}
pub fn retain(&mut self, mut f: impl FnMut(EpubTocEntry) -> bool) {
self.toc
.entries
.retain(|key, entry| f(self.ctx.create_root(key.version, entry)));
}
pub fn extract_if(
&mut self,
mut f: impl FnMut(EpubTocEntry) -> bool,
) -> impl Iterator<Item = DetachedEpubTocEntry> {
let ctx = self.ctx;
self.toc
.entries
.extract_if(.., move |key, data| f(ctx.create_root(key.version, data)))
.map(|(key, data)| DetachedEpubTocEntry::detached(key.version, data))
}
pub fn drain(&mut self) -> impl Iterator<Item = DetachedEpubTocEntry> {
self.toc
.entries
.drain(..)
.map(|(key, data)| DetachedEpubTocEntry::detached(key.version, data))
}
pub fn clear(&mut self) {
self.toc.entries.clear();
}
pub fn as_view(&mut self) -> EpubToc<'_> {
self.ctx.create(self.toc)
}
}
impl Debug for EpubTocMut<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EpubTocMut")
.field("href_resolver", &self.href_resolver)
.field("toc", &self.toc)
.finish_non_exhaustive()
}
}
impl<'a, 'ebook: 'a> IntoIterator for &'a mut EpubTocMut<'ebook> {
type Item = EpubTocEntryMut<'a>;
type IntoIter = EpubTocIterMut<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter_mut()
}
}
impl<'ebook> IntoIterator for EpubTocMut<'ebook> {
type Item = EpubTocEntryMut<'ebook>;
type IntoIter = EpubTocIterMut<'ebook>;
fn into_iter(self) -> Self::IntoIter {
EpubTocIterMut {
ctx: self.ctx,
href_resolver: self.href_resolver,
iter: self.toc.entries.iter_mut(),
}
}
}
pub struct EpubTocIterMut<'ebook> {
ctx: EpubTocContext<'ebook>,
href_resolver: UriResolver<'ebook>,
iter: indexmap::map::IterMut<'ebook, EpubTocKey, EpubTocEntryData>,
}
impl Debug for EpubTocIterMut<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EpubTocIterMut")
.field("href_resolver", &self.href_resolver)
.field("iter", &self.iter)
.finish_non_exhaustive()
}
}
impl<'ebook> Iterator for EpubTocIterMut<'ebook> {
type Item = EpubTocEntryMut<'ebook>;
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|(key, root)| {
self.ctx
.create_root_mut(key.version, Some(self.href_resolver), root)
})
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
pub struct EpubTocEntryMut<'ebook> {
ctx: EpubTocContext<'ebook>,
version: EpubVersion,
href_resolver: Option<UriResolver<'ebook>>,
data: &'ebook mut EpubTocEntryData,
depth: usize,
}
impl<'ebook> EpubTocEntryMut<'ebook> {
fn new(
ctx: EpubTocContext<'ebook>,
version: EpubVersion,
href_resolver: Option<UriResolver<'ebook>>,
data: &'ebook mut EpubTocEntryData,
depth: usize,
) -> Self {
Self {
ctx,
version,
href_resolver,
data,
depth,
}
}
fn insert_detached(
&mut self,
index: usize,
detached: impl Iterator<Item = DetachedEpubTocEntry>,
) {
let children = &mut self.data.children;
let detached = detached.map(|mut entry| {
if let Some(resolver) = self.href_resolver {
entry.data.resolve_hrefs(resolver);
}
entry
});
match detached.one_or_many() {
OneOrMany::None => { }
OneOrMany::One(entry) => {
children.insert(index, entry.data);
}
OneOrMany::Many(many) => {
children.splice(index..index, many.map(|e| e.data));
}
}
}
pub fn set_id(&mut self, id: impl IntoOption<String>) -> Option<String> {
std::mem::replace(&mut self.data.id, id.into_option())
}
pub fn set_label(&mut self, label: impl Into<String>) -> String {
std::mem::replace(&mut self.data.label, label.into())
}
pub fn set_kind(&mut self, kind: impl IntoOption<String>) -> Option<String> {
if self.depth == 0 && self.href_resolver.is_some() {
None
} else {
std::mem::replace(&mut self.data.kind, kind.into_option())
}
}
pub fn set_href(&mut self, raw_href: impl IntoOption<String>) -> Option<String> {
let data = &mut *self.data;
if let Some(href_raw) = raw_href.into_option() {
data.href = self
.href_resolver
.map(|resolver| resolver.resolve(&href_raw));
data.href_raw.replace(href_raw)
} else {
data.href = None;
data.href_raw.take()
}
}
pub fn attributes_mut(&mut self) -> &mut Attributes {
&mut self.data.attributes
}
pub fn push(&mut self, detached: impl Many<DetachedEpubTocEntry>) {
self.insert(self.data.children.len(), detached);
}
pub fn insert(&mut self, index: usize, detached: impl Many<DetachedEpubTocEntry>) {
self.insert_detached(index, detached.iter_many());
}
pub fn get_mut(&mut self, index: usize) -> Option<EpubTocEntryMut<'_>> {
self.data.children.get_mut(index).map(|data| {
self.ctx
.create_entry_mut(self.version, self.href_resolver, data, self.depth + 1)
})
}
pub fn iter_mut(&mut self) -> EpubTocEntryIterMut<'_> {
EpubTocEntryIterMut {
ctx: self.ctx,
version: self.version,
href_resolver: self.href_resolver,
next_depth: self.depth + 1,
iter: self.data.children.iter_mut(),
}
}
pub fn remove(&mut self, index: usize) -> DetachedEpubTocEntry {
DetachedEpubTocEntry::detached(self.version, self.data.children.remove(index))
}
pub fn retain(&mut self, mut f: impl FnMut(EpubTocEntry<'_>) -> bool) {
self.data
.children
.retain(|child| f(self.ctx.create_entry(self.version, child, self.depth + 1)));
}
pub fn extract_if(
&mut self,
mut f: impl FnMut(EpubTocEntry<'_>) -> bool,
) -> impl Iterator<Item = DetachedEpubTocEntry> {
let ctx = self.ctx;
let version = self.version;
let next_depth = self.depth + 1;
self.data
.children
.extract_if(.., move |child| {
f(ctx.create_entry(version, child, next_depth))
})
.map(|data| DetachedEpubTocEntry::detached(self.version, data))
}
pub fn drain(
&mut self,
range: impl std::ops::RangeBounds<usize>,
) -> impl Iterator<Item = DetachedEpubTocEntry> {
self.data
.children
.drain(range)
.map(|data| DetachedEpubTocEntry::detached(self.version, data))
}
pub fn clear(&mut self) {
self.data.children.clear();
}
pub fn as_view(&self) -> EpubTocEntry<'_> {
self.ctx.create_entry(self.version, self.data, self.depth)
}
}
impl Debug for EpubTocEntryMut<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EpubTocEntryMut")
.field("href_resolver", &self.href_resolver)
.field("version", &self.version)
.field("depth", &self.depth)
.field("data", &self.data)
.finish_non_exhaustive()
}
}
impl Extend<DetachedEpubTocEntry> for EpubTocEntryMut<'_> {
fn extend<T: IntoIterator<Item = DetachedEpubTocEntry>>(&mut self, iter: T) {
self.data.children.extend(iter.into_iter().map(|e| e.data));
}
}
impl<'a, 'ebook: 'a> IntoIterator for &'a mut EpubTocEntryMut<'ebook> {
type Item = EpubTocEntryMut<'a>;
type IntoIter = EpubTocEntryIterMut<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter_mut()
}
}
impl<'ebook> IntoIterator for EpubTocEntryMut<'ebook> {
type Item = Self;
type IntoIter = EpubTocEntryIterMut<'ebook>;
fn into_iter(self) -> Self::IntoIter {
EpubTocEntryIterMut {
ctx: self.ctx,
version: self.version,
href_resolver: self.href_resolver,
next_depth: self.depth + 1,
iter: self.data.children.iter_mut(),
}
}
}
pub struct EpubTocEntryIterMut<'ebook> {
ctx: EpubTocContext<'ebook>,
version: EpubVersion,
href_resolver: Option<UriResolver<'ebook>>,
next_depth: usize,
iter: std::slice::IterMut<'ebook, EpubTocEntryData>,
}
impl<'ebook> Iterator for EpubTocEntryIterMut<'ebook> {
type Item = EpubTocEntryMut<'ebook>;
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|entry| {
self.ctx
.create_entry_mut(self.version, self.href_resolver, entry, self.next_depth)
})
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
impl Debug for EpubTocEntryIterMut<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EpubTocEntryIterMut")
.field("href_resolver", &self.href_resolver)
.field("version", &self.version)
.field("next_depth", &self.next_depth)
.field("iter", &self.iter)
.finish_non_exhaustive()
}
}