#[cfg(feature = "write")]
mod write;
use crate::ebook::element::{
Attribute, Attributes, AttributesData, Href, Name, Properties, TextDirection,
};
use crate::ebook::metadata::datetime::DateTime;
use crate::ebook::metadata::{
AlternateScript, Contributor, Identifier, Language, LanguageKind, LanguageTag, MetaEntry,
Metadata, Scheme, Tag, Title, TitleKind, Version,
};
use crate::ebook::resource::ResourceKind;
use crate::epub::consts::{dc, opf};
use crate::epub::metadata::macros::{impl_meta_entry, impl_meta_entry_abstraction};
use crate::epub::package::{EpubPackageData, EpubPackageMetaContext};
use crate::util::{Sealed, doc};
use indexmap::IndexMap;
use std::fmt::Display;
use std::iter::{Enumerate, FlatMap};
use std::slice::Iter as SliceIter;
#[cfg(feature = "write")]
pub use write::{
DetachedEpubMetaEntry, EpubMetaEntryMut, EpubMetadataIterMut, EpubMetadataMut,
EpubRefinementsIterMut, EpubRefinementsMut, marker,
};
pub(super) type EpubMetaGroups = IndexMap<String, Vec<EpubMetaEntryData>>;
#[derive(Debug, PartialEq)]
pub(super) struct EpubMetadataData {
pub(super) entries: EpubMetaGroups,
}
impl EpubMetadataData {
pub(super) fn new(entries: EpubMetaGroups) -> Self {
Self { entries }
}
pub(super) fn empty() -> Self {
Self::new(IndexMap::new())
}
pub(super) fn epub2_cover_image_id(&self) -> Option<&str> {
self.entries
.get(opf::COVER)
.and_then(|group| group.first())
.map(|cover| cover.value.as_str())
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub(super) struct EpubRefinementsData(Vec<EpubMetaEntryData>);
impl EpubRefinementsData {
pub(crate) fn new(refinements: Vec<EpubMetaEntryData>) -> Self {
Self(refinements)
}
fn get_schemes(&self, key: &str) -> impl Iterator<Item = Scheme<'_>> {
self.by_refinements(key).map(|(_, key_item)| {
let scheme = key_item.attributes.get_value(opf::SCHEME);
Scheme::new(scheme, &key_item.value)
})
}
fn by_refinements(&self, property: &str) -> impl Iterator<Item = (usize, &EpubMetaEntryData)> {
self.0
.iter()
.enumerate()
.filter(move |(_, refinement)| refinement.property == property)
}
pub(crate) fn has_refinement(&self, property: &str) -> bool {
self.0
.iter()
.any(|refinement| refinement.property == property)
}
pub(crate) fn by_refinement(&self, property: &str) -> Option<&EpubMetaEntryData> {
self.0
.iter()
.find(|refinement| refinement.property == property)
}
}
impl std::ops::Deref for EpubRefinementsData {
type Target = Vec<EpubMetaEntryData>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::ops::DerefMut for EpubRefinementsData {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[derive(Clone, Debug, PartialEq)]
pub(super) struct EpubMetaEntryData {
pub(super) id: Option<String>,
pub(super) property: String,
pub(super) value: String,
pub(super) language: Option<String>,
pub(super) text_direction: TextDirection,
pub(super) attributes: AttributesData,
pub(super) refinements: EpubRefinementsData,
pub(super) kind: EpubMetaEntryKind,
}
impl Default for EpubMetaEntryData {
fn default() -> Self {
Self {
id: None,
property: String::new(),
value: String::new(),
language: None,
text_direction: TextDirection::Auto,
attributes: AttributesData::default(),
refinements: EpubRefinementsData::default(),
kind: EpubMetaEntryKind::Meta {
version: EpubVersion::EPUB3,
},
}
}
}
impl EpubMetaEntryData {
fn language(&self) -> Option<&str> {
self.language.as_deref()
}
}
#[derive(Copy, Clone, Debug)]
pub struct EpubMetadata<'ebook> {
ctx: EpubPackageMetaContext<'ebook>,
package: &'ebook EpubPackageData,
data: &'ebook EpubMetadataData,
}
impl<'ebook> EpubMetadata<'ebook> {
pub(super) fn new(package: &'ebook EpubPackageData, data: &'ebook EpubMetadataData) -> Self {
Self {
ctx: EpubPackageMetaContext::new(package),
package,
data,
}
}
fn data_by_property(
&self,
property: &str,
) -> impl Iterator<Item = (usize, &'ebook EpubMetaEntryData)> + 'ebook {
self.data
.entries
.get(property)
.map_or::<&[_], _>(&[], Vec::as_slice)
.iter()
.enumerate()
}
pub fn by_property(
&self,
property: &str,
) -> impl Iterator<Item = EpubMetaEntry<'ebook>> + 'ebook {
let ctx = self.ctx;
self.data_by_property(property)
.map(move |(i, data)| ctx.create_entry(data, i))
}
pub fn by_id(&self, id: &str) -> Option<EpubMetaEntry<'ebook>> {
fn dfs_by_id<'a>(
id: &str,
entry: &'a EpubMetaEntryData,
index: usize,
) -> Option<(usize, Option<&'a str>, &'a EpubMetaEntryData)> {
if entry.id.as_deref() == Some(id) {
return Some((index, None, entry));
}
for (i, refinement) in entry.refinements.iter().enumerate() {
if let Some(mut found) = dfs_by_id(id, refinement, i) {
if refinement == found.2 {
found.1 = entry.id.as_deref();
}
return Some(found);
}
}
None
}
self.data
.entries
.values()
.flat_map(|group| group.iter().enumerate())
.find_map(|(i, entry)| dfs_by_id(id, entry, i))
.map(|(i, refines, data)| self.ctx.create_refining_entry(refines, data, i))
}
pub fn version(&self) -> EpubVersion {
self.package.version.parsed
}
pub fn version_str(&self) -> &'ebook str {
self.package.version.raw.as_str()
}
pub fn links(&self) -> impl Iterator<Item = EpubLink<'ebook>> + 'ebook {
self.iter().filter_map(|entry| entry.as_link())
}
#[doc = doc::inherent!(Metadata, published)]
pub fn published(&self) -> Option<DateTime> {
self.published_entry()
.and_then(|entry| DateTime::parse(entry.value()))
}
#[doc = doc::inherent!(Metadata, modified)]
pub fn modified(&self) -> Option<DateTime> {
self.modified_entry()
.and_then(|entry| DateTime::parse(entry.value()))
}
pub fn published_entry(&self) -> Option<EpubMetaEntry<'ebook>> {
let mut inferred_date = None;
for (i, date) in self.data_by_property(dc::DATE) {
match date.attributes.by_name(opf::OPF_EVENT) {
Some(opf_event) if opf_event.value() == opf::PUBLICATION => {
return Some(self.ctx.create_entry(date, i));
}
None if inferred_date.is_none() => inferred_date = Some((i, date)),
_ => {}
}
}
inferred_date.map(|(i, date)| self.ctx.create_entry(date, i))
}
pub fn modified_entry(&self) -> Option<EpubMetaEntry<'ebook>> {
if let Some((i, modified_date)) = self.data_by_property(dc::MODIFIED).next() {
return Some(self.ctx.create_entry(modified_date, i));
}
self.data_by_property(dc::DATE)
.find(|(_, date)| {
date.attributes
.get_value(opf::OPF_EVENT)
.is_some_and(|opf_event| opf_event == opf::MODIFICATION)
})
.map(|(i, date)| self.ctx.create_entry(date, i))
}
#[doc = doc::inherent!(Metadata, identifier)]
pub fn identifier(&self) -> Option<EpubIdentifier<'ebook>> {
self.data_by_property(dc::IDENTIFIER)
.find(|(_, data)| data.id.as_ref() == Some(&self.package.unique_identifier))
.map(|(i, data)| EpubIdentifier::new(self.ctx.create_entry(data, i)))
}
#[doc = doc::inherent!(Metadata, identifiers)]
pub fn identifiers(&self) -> impl Iterator<Item = EpubIdentifier<'ebook>> + 'ebook {
let ctx = self.ctx;
self.data_by_property(dc::IDENTIFIER)
.map(move |(i, data)| EpubIdentifier::new(ctx.create_entry(data, i)))
}
#[doc = doc::inherent!(Metadata, language)]
pub fn language(&self) -> Option<EpubLanguage<'ebook>> {
self.languages().next()
}
#[doc = doc::inherent!(Metadata, languages)]
pub fn languages(&self) -> impl Iterator<Item = EpubLanguage<'ebook>> + 'ebook {
let ctx = self.ctx;
self.data_by_property(dc::LANGUAGE)
.map(move |(i, data)| EpubLanguage::new(ctx.create_entry(data, i)))
}
#[doc = doc::inherent!(Metadata, title)]
pub fn title(&self) -> Option<EpubTitle<'ebook>> {
self.titles().find(|title| title.is_main_title)
}
#[doc = doc::inherent!(Metadata, titles)]
pub fn titles(&self) -> impl Iterator<Item = EpubTitle<'ebook>> + 'ebook {
let ctx = self.ctx;
let inferred_main_title_index = self
.data_by_property(dc::TITLE)
.position(|(_, title)| {
title
.refinements
.by_refinement(opf::TITLE_TYPE)
.is_some_and(|title_type| title_type.value == opf::MAIN_TITLE_TYPE)
})
.unwrap_or(0);
self.data_by_property(dc::TITLE).map(move |(i, data)| {
EpubTitle::new(ctx.create_entry(data, i), i == inferred_main_title_index)
})
}
#[doc = doc::inherent!(Metadata, description)]
pub fn description(&self) -> Option<EpubMetaEntry<'ebook>> {
self.descriptions().next()
}
#[doc = doc::inherent!(Metadata, descriptions)]
pub fn descriptions(&self) -> impl Iterator<Item = EpubMetaEntry<'ebook>> + 'ebook {
let ctx = self.ctx;
self.data_by_property(dc::DESCRIPTION)
.map(move |(i, data)| ctx.create_entry(data, i))
}
#[doc = doc::inherent!(Metadata, creators)]
pub fn creators(&self) -> impl Iterator<Item = EpubContributor<'ebook>> + 'ebook {
let ctx = self.ctx;
self.data_by_property(dc::CREATOR)
.map(move |(i, data)| EpubContributor::new(ctx.create_entry(data, i)))
}
#[doc = doc::inherent!(Metadata, contributors)]
pub fn contributors(&self) -> impl Iterator<Item = EpubContributor<'ebook>> + 'ebook {
let ctx = self.ctx;
self.data_by_property(dc::CONTRIBUTOR)
.map(move |(i, data)| EpubContributor::new(ctx.create_entry(data, i)))
}
#[doc = doc::inherent!(Metadata, publishers)]
pub fn publishers(&self) -> impl Iterator<Item = EpubContributor<'ebook>> + 'ebook {
let ctx = self.ctx;
self.data_by_property(dc::PUBLISHER)
.map(move |(i, data)| EpubContributor::new(ctx.create_entry(data, i)))
}
#[doc = doc::inherent!(Metadata, generators)]
pub fn generators(&self) -> impl Iterator<Item = EpubMetaEntry<'ebook>> + 'ebook {
self.by_property(opf::GENERATOR)
}
#[doc = doc::inherent!(Metadata, tags)]
pub fn tags(&self) -> impl Iterator<Item = EpubTag<'ebook>> + 'ebook {
let ctx = self.ctx;
self.data_by_property(dc::SUBJECT)
.map(move |(i, data)| EpubTag::new(ctx.create_entry(data, i)))
}
#[doc = doc::inherent!(Metadata, iter)]
pub fn iter(&self) -> EpubMetadataIter<'ebook> {
EpubMetadataIter {
ctx: self.ctx,
iter: self
.data
.entries
.values()
.flat_map(|group| group.iter().enumerate()),
}
}
}
impl Sealed for EpubMetadata<'_> {}
#[allow(refining_impl_trait)]
impl<'ebook> Metadata<'ebook> for EpubMetadata<'ebook> {
fn version_str(&self) -> Option<&'ebook str> {
Some(self.version_str())
}
fn version(&self) -> Option<Version> {
Some(self.version().version())
}
fn published(&self) -> Option<DateTime> {
self.published()
}
fn modified(&self) -> Option<DateTime> {
self.modified()
}
fn identifier(&self) -> Option<EpubIdentifier<'ebook>> {
self.identifier()
}
fn identifiers(&self) -> impl Iterator<Item = EpubIdentifier<'ebook>> + 'ebook {
self.identifiers()
}
fn language(&self) -> Option<EpubLanguage<'ebook>> {
self.language()
}
fn languages(&self) -> impl Iterator<Item = EpubLanguage<'ebook>> + 'ebook {
self.languages()
}
fn title(&self) -> Option<EpubTitle<'ebook>> {
self.title()
}
fn titles(&self) -> impl Iterator<Item = EpubTitle<'ebook>> + 'ebook {
self.titles()
}
fn description(&self) -> Option<EpubMetaEntry<'ebook>> {
self.description()
}
fn descriptions(&self) -> impl Iterator<Item = EpubMetaEntry<'ebook>> + 'ebook {
self.descriptions()
}
fn creators(&self) -> impl Iterator<Item = EpubContributor<'ebook>> + 'ebook {
self.creators()
}
fn contributors(&self) -> impl Iterator<Item = EpubContributor<'ebook>> + 'ebook {
self.contributors()
}
fn publishers(&self) -> impl Iterator<Item = EpubContributor<'ebook>> + 'ebook {
self.publishers()
}
fn generators(&self) -> impl Iterator<Item = impl MetaEntry<'ebook> + 'ebook> + 'ebook {
self.generators()
}
fn tags(&self) -> impl Iterator<Item = EpubTag<'ebook>> + 'ebook {
self.tags()
}
fn iter(&self) -> EpubMetadataIter<'ebook> {
self.iter()
}
}
impl PartialEq for EpubMetadata<'_> {
fn eq(&self, other: &Self) -> bool {
self.data == other.data
}
}
impl<'ebook> IntoIterator for &EpubMetadata<'ebook> {
type Item = EpubMetaEntry<'ebook>;
type IntoIter = EpubMetadataIter<'ebook>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'ebook> IntoIterator for EpubMetadata<'ebook> {
type Item = EpubMetaEntry<'ebook>;
type IntoIter = EpubMetadataIter<'ebook>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
pub(super) type InnerMetadataIter<ValuesIter, SliceIter, MapArgs> =
FlatMap<ValuesIter, Enumerate<SliceIter>, fn(MapArgs) -> Enumerate<SliceIter>>;
pub struct EpubMetadataIter<'ebook> {
ctx: EpubPackageMetaContext<'ebook>,
iter: InnerMetadataIter<
indexmap::map::Values<'ebook, String, Vec<EpubMetaEntryData>>,
SliceIter<'ebook, EpubMetaEntryData>,
&'ebook Vec<EpubMetaEntryData>,
>,
}
impl<'ebook> Iterator for EpubMetadataIter<'ebook> {
type Item = EpubMetaEntry<'ebook>;
fn next(&mut self) -> Option<Self::Item> {
self.iter
.next()
.map(|(i, data)| self.ctx.create_entry(data, i))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
#[derive(Copy, Clone, Debug)]
pub struct EpubRefinements<'ebook> {
ctx: EpubPackageMetaContext<'ebook>,
parent_id: Option<&'ebook str>,
data: &'ebook EpubRefinementsData,
}
impl<'ebook> EpubRefinements<'ebook> {
pub(super) fn new(
ctx: EpubPackageMetaContext<'ebook>,
parent_id: Option<&'ebook str>,
data: &'ebook EpubRefinementsData,
) -> Self {
Self {
ctx,
parent_id,
data,
}
}
pub fn len(&self) -> usize {
self.data.len()
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
pub fn get(&self, index: usize) -> Option<EpubMetaEntry<'ebook>> {
self.data
.get(index)
.map(|data| self.ctx.create_entry(data, index))
}
pub fn iter(&self) -> EpubRefinementsIter<'ebook> {
EpubRefinementsIter {
ctx: self.ctx,
parent_id: self.parent_id,
iter: self.data.iter().enumerate(),
}
}
pub fn by_property(
&self,
property: &'ebook str,
) -> impl Iterator<Item = EpubMetaEntry<'ebook>> + 'ebook {
let ctx = self.ctx;
let parent_id = self.parent_id;
self.data
.by_refinements(property)
.map(move |(i, data)| ctx.create_refining_entry(parent_id, data, i))
}
pub fn has_property(&self, property: &str) -> bool {
self.data.has_refinement(property)
}
}
impl PartialEq for EpubRefinements<'_> {
fn eq(&self, other: &Self) -> bool {
self.data == other.data
}
}
impl<'ebook> IntoIterator for &EpubRefinements<'ebook> {
type Item = EpubMetaEntry<'ebook>;
type IntoIter = EpubRefinementsIter<'ebook>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'ebook> IntoIterator for EpubRefinements<'ebook> {
type Item = EpubMetaEntry<'ebook>;
type IntoIter = EpubRefinementsIter<'ebook>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
pub struct EpubRefinementsIter<'ebook> {
ctx: EpubPackageMetaContext<'ebook>,
parent_id: Option<&'ebook str>,
iter: Enumerate<std::slice::Iter<'ebook, EpubMetaEntryData>>,
}
impl<'ebook> Iterator for EpubRefinementsIter<'ebook> {
type Item = EpubMetaEntry<'ebook>;
fn next(&mut self) -> Option<Self::Item> {
self.iter
.next()
.map(|(i, data)| self.ctx.create_refining_entry(self.parent_id, data, i))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
#[derive(Copy, Clone, Debug)]
pub struct EpubMetaEntry<'ebook> {
ctx: EpubPackageMetaContext<'ebook>,
refines: Option<&'ebook str>,
data: &'ebook EpubMetaEntryData,
index: usize,
}
impl_meta_entry!(EpubMetaEntry);
impl<'ebook> EpubMetaEntry<'ebook> {
pub(super) fn new(
ctx: EpubPackageMetaContext<'ebook>,
refines: Option<&'ebook str>,
data: &'ebook EpubMetaEntryData,
index: usize,
) -> Self {
Self {
ctx,
refines,
data,
index,
}
}
fn data(&self) -> &'ebook EpubMetaEntryData {
self.data
}
fn index(&self) -> usize {
self.index
}
pub fn id(&self) -> Option<&'ebook str> {
self.data.id.as_deref()
}
pub fn refines(&self) -> Option<&'ebook str> {
self.refines
}
pub fn property(&self) -> Name<'ebook> {
Name::new(&self.data.property)
}
pub fn scheme(&self) -> Scheme<'ebook> {
if let Some(code) = self.data.attributes.get_value(opf::OPF_SCHEME) {
return Scheme::new(None, code);
}
Scheme::new(self.data.attributes.get_value(opf::SCHEME), self.value())
}
pub fn xml_language(&self) -> Option<LanguageTag<'ebook>> {
self.data
.language()
.or_else(|| self.ctx.package_language())
.map(|code| LanguageTag::new(code, LanguageKind::Bcp47))
}
pub fn text_direction(&self) -> TextDirection {
match self.data.text_direction {
TextDirection::Auto => self.ctx.package_text_direction(),
text_direction => text_direction,
}
}
pub fn attributes(&self) -> &'ebook Attributes {
&self.data.attributes
}
pub fn refinements(&self) -> EpubRefinements<'ebook> {
self.ctx
.create_refinements(self.id(), &self.data.refinements)
}
pub fn kind(&self) -> EpubMetaEntryKind {
self.data.kind
}
pub fn as_link(&self) -> Option<EpubLink<'ebook>> {
self.kind().is_link().then_some(EpubLink(*self))
}
}
impl PartialEq for EpubMetaEntry<'_> {
fn eq(&self, other: &Self) -> bool {
self.data == other.data
}
}
pub struct EpubLink<'ebook>(EpubMetaEntry<'ebook>);
impl<'ebook> EpubLink<'ebook> {
pub fn href(&self) -> Option<Href<'ebook>> {
self.0.data.attributes.get_value(opf::HREF).map(Href::new)
}
pub fn href_lang(&self) -> Option<LanguageTag<'ebook>> {
self.0
.data
.attributes
.get_value(opf::HREFLANG)
.map(|hreflang| LanguageTag::new(hreflang, LanguageKind::Bcp47))
}
pub fn media_type(&self) -> Option<&'ebook str> {
self.0.data.attributes.get_value(opf::MEDIA_TYPE)
}
pub fn kind(&self) -> Option<ResourceKind<'ebook>> {
self.media_type().map(Into::into)
}
pub fn properties(&self) -> &'ebook Properties {
self.0
.data
.attributes
.by_name(opf::PROPERTIES)
.map_or(Properties::EMPTY_REFERENCE, Attribute::as_properties)
}
pub fn rel(&self) -> &'ebook Properties {
self.0
.data
.attributes
.by_name(opf::REL)
.map_or(Properties::EMPTY_REFERENCE, Attribute::as_properties)
}
pub fn as_meta(&self) -> EpubMetaEntry<'_> {
self.0
}
}
#[non_exhaustive]
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub enum EpubMetaEntryKind {
#[non_exhaustive]
DublinCore {},
#[non_exhaustive]
Meta {
version: EpubVersion,
},
#[non_exhaustive]
Link {},
}
impl EpubMetaEntryKind {
pub fn is_dublin_core(&self) -> bool {
matches!(self, Self::DublinCore { .. })
}
pub fn is_meta(&self) -> bool {
matches!(self, Self::Meta { .. })
}
pub fn is_epub2_meta(&self) -> bool {
matches!(
self,
Self::Meta {
version: EpubVersion::Epub2(_),
}
)
}
pub fn is_epub3_meta(&self) -> bool {
matches!(
self,
Self::Meta {
version: EpubVersion::Epub3(_),
}
)
}
pub fn is_link(&self) -> bool {
matches!(self, Self::Link { .. })
}
pub fn version(&self) -> Option<EpubVersion> {
match self {
Self::Meta { version, .. } => Some(*version),
_ => None,
}
}
}
#[non_exhaustive]
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub enum EpubVersion {
Epub2(Version),
Epub3(Version),
Unknown(Version),
}
impl EpubVersion {
pub(crate) const VERSIONS: [Self; 2] = [Self::EPUB2, Self::EPUB3];
pub const EPUB2: Self = Self::Epub2(Version(2, 0));
pub const EPUB3: Self = Self::Epub3(Version(3, 0));
pub fn as_major(&self) -> Self {
match self {
Self::Epub2(_) => Self::EPUB2,
Self::Epub3(_) => Self::EPUB3,
Self::Unknown(version) => Self::Unknown(Version(version.0, 0)),
}
}
pub fn version(&self) -> Version {
match self {
Self::Epub2(version) | Self::Epub3(version) | Self::Unknown(version) => *version,
}
}
pub fn is_epub2(&self) -> bool {
matches!(self, Self::Epub2(_))
}
pub fn is_epub3(&self) -> bool {
matches!(self, Self::Epub3(_))
}
pub fn is_unknown(&self) -> bool {
matches!(self, Self::Unknown(_))
}
}
impl PartialOrd for EpubVersion {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for EpubVersion {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.version().cmp(&other.version())
}
}
impl<I: Into<Version>> From<I> for EpubVersion {
fn from(version: I) -> Self {
let version = version.into();
match version.0 {
2 => Self::Epub2(version),
3 => Self::Epub3(version),
_ => Self::Unknown(version),
}
}
}
impl Display for EpubVersion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.version().fmt(f)
}
}
#[derive(Copy, Clone, Debug)]
pub struct EpubIdentifier<'ebook> {
entry: EpubMetaEntry<'ebook>,
}
impl_meta_entry_abstraction!(EpubIdentifier);
impl<'ebook> EpubIdentifier<'ebook> {
fn new(entry: EpubMetaEntry<'ebook>) -> Self {
Self { entry }
}
fn get_legacy_identifier_type(&self) -> Option<Scheme<'ebook>> {
self.entry
.data
.attributes
.get_value(opf::OPF_SCHEME)
.map(|identifier_type| Scheme::new(None, identifier_type))
}
fn get_modern_identifier_type(&self) -> Option<Scheme<'ebook>> {
self.entry
.data
.refinements
.get_schemes(opf::IDENTIFIER_TYPE)
.next()
}
#[doc = doc::inherent!(Identifier, scheme)]
pub fn scheme(&self) -> Option<Scheme<'ebook>> {
self.get_modern_identifier_type()
.or_else(|| self.get_legacy_identifier_type())
}
}
impl<'ebook> Identifier<'ebook> for EpubIdentifier<'ebook> {
fn scheme(&self) -> Option<Scheme<'ebook>> {
self.scheme()
}
}
impl Eq for EpubIdentifier<'_> {}
impl PartialEq for EpubIdentifier<'_> {
fn eq(&self, other: &Self) -> bool {
self.scheme() == other.scheme() && self.value() == other.value()
}
}
impl std::hash::Hash for EpubIdentifier<'_> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.value().hash(state);
self.scheme().hash(state);
}
}
#[derive(Copy, Clone, Debug)]
pub struct EpubTitle<'ebook> {
entry: EpubMetaEntry<'ebook>,
is_main_title: bool,
}
impl_meta_entry_abstraction!(EpubTitle);
impl<'ebook> EpubTitle<'ebook> {
fn new(entry: EpubMetaEntry<'ebook>, is_main_title: bool) -> Self {
Self {
entry,
is_main_title,
}
}
#[doc = doc::inherent!(Title, scheme)]
pub fn scheme(&self) -> Option<Scheme<'ebook>> {
self.entry
.data
.refinements
.by_refinement(opf::TITLE_TYPE)
.map(|title_type| Scheme::new(None, &title_type.value))
}
#[doc = doc::inherent!(Title, kind)]
pub fn kind(&self) -> TitleKind {
if self.is_main_title {
return TitleKind::Main;
}
self.entry
.data
.refinements
.by_refinement(opf::TITLE_TYPE)
.map_or(TitleKind::Unknown, |title_type| {
TitleKind::from_str(&title_type.value)
})
}
}
impl<'ebook> Title<'ebook> for EpubTitle<'ebook> {
fn scheme(&self) -> Option<Scheme<'ebook>> {
self.scheme()
}
fn kind(&self) -> TitleKind {
self.kind()
}
}
impl PartialEq for EpubTitle<'_> {
fn eq(&self, other: &Self) -> bool {
self.entry == other.entry
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct EpubTag<'ebook> {
entry: EpubMetaEntry<'ebook>,
}
impl_meta_entry_abstraction!(EpubTag);
impl<'ebook> EpubTag<'ebook> {
fn new(entry: EpubMetaEntry<'ebook>) -> Self {
Self { entry }
}
fn get_legacy_scheme(&self) -> Option<Scheme<'ebook>> {
let refinements = &self.entry.data.refinements;
let auth = refinements.by_refinement(opf::AUTHORITY)?;
let term = refinements.by_refinement(opf::TERM)?;
Some(Scheme::new(Some(&auth.value), &term.value))
}
fn get_modern_scheme(&self) -> Option<Scheme<'ebook>> {
let attributes = &self.entry.data.attributes;
let authority = attributes.get_value(opf::OPF_AUTHORITY)?;
let term = attributes.get_value(opf::OPF_TERM)?;
Some(Scheme::new(Some(authority), term))
}
#[doc = doc::inherent!(Tag, scheme)]
pub fn scheme(&self) -> Option<Scheme<'ebook>> {
self.get_modern_scheme()
.or_else(|| self.get_legacy_scheme())
}
}
impl<'ebook> Tag<'ebook> for EpubTag<'ebook> {
fn scheme(&self) -> Option<Scheme<'ebook>> {
self.scheme()
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct EpubContributor<'ebook> {
entry: EpubMetaEntry<'ebook>,
}
impl_meta_entry_abstraction!(EpubContributor);
impl<'ebook> EpubContributor<'ebook> {
fn new(entry: EpubMetaEntry<'ebook>) -> Self {
Self { entry }
}
fn get_legacy_role(&self) -> Option<Scheme<'ebook>> {
self.entry
.data
.attributes
.by_name(opf::OPF_ROLE)
.map(|role| Scheme::new(None, role.value()))
}
fn get_modern_roles(&self) -> impl Iterator<Item = Scheme<'ebook>> + 'ebook {
self.entry.data.refinements.get_schemes(opf::ROLE)
}
#[doc = doc::inherent!(Contributor, main_role)]
pub fn main_role(&self) -> Option<Scheme<'ebook>> {
self.roles().next()
}
#[doc = doc::inherent!(Contributor, roles)]
pub fn roles(&self) -> impl Iterator<Item = Scheme<'ebook>> + 'ebook {
let mut roles = self.get_modern_roles().peekable();
let fallback = if roles.peek().is_none() {
self.get_legacy_role()
} else {
None
};
roles.chain(fallback)
}
}
impl<'ebook> Contributor<'ebook> for EpubContributor<'ebook> {
fn main_role(&self) -> Option<Scheme<'ebook>> {
self.main_role()
}
fn roles(&self) -> impl Iterator<Item = Scheme<'ebook>> + 'ebook {
self.roles()
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct EpubLanguage<'ebook> {
entry: EpubMetaEntry<'ebook>,
}
impl_meta_entry_abstraction!(EpubLanguage);
impl<'ebook> EpubLanguage<'ebook> {
fn new(entry: EpubMetaEntry<'ebook>) -> Self {
Self { entry }
}
}
impl<'ebook> EpubLanguage<'ebook> {
#[doc = doc::inherent!(Language, scheme)]
pub fn scheme(&self) -> Scheme<'ebook> {
Scheme::new(Some(LanguageKind::Bcp47.as_str()), &self.entry.data.value)
}
#[doc = doc::inherent!(Language, kind)]
pub fn kind(&self) -> LanguageKind {
LanguageKind::Bcp47
}
}
impl<'ebook> Language<'ebook> for EpubLanguage<'ebook> {
fn scheme(&self) -> Scheme<'ebook> {
self.scheme()
}
fn kind(&self) -> LanguageKind {
self.kind()
}
}
mod macros {
macro_rules! impl_meta_entry_abstraction {
($implementation:ident) => {
impl<'ebook> $implementation<'ebook> {
fn data(&self) -> &'ebook EpubMetaEntryData {
&self.entry.data
}
fn index(&self) -> usize {
self.entry.index
}
pub fn as_meta(&self) -> EpubMetaEntry<'ebook> {
self.entry
}
}
impl PartialEq<EpubMetaEntry<'_>> for $implementation<'_> {
fn eq(&self, other_entry: &EpubMetaEntry<'_>) -> bool {
&self.entry == other_entry
}
}
impl<'ebook> PartialEq<$implementation<'ebook>> for EpubMetaEntry<'_> {
fn eq(&self, other: &$implementation<'ebook>) -> bool {
self == &other.entry
}
}
impl<'ebook> std::ops::Deref for $implementation<'ebook> {
type Target = EpubMetaEntry<'ebook>;
fn deref(&self) -> &Self::Target {
&self.entry
}
}
impl_meta_entry!($implementation);
};
}
macro_rules! impl_meta_entry {
($implementation:ident) => {
impl<'ebook> $implementation<'ebook> {
fn get_modern_alt_script(
&self,
) -> impl Iterator<Item = AlternateScript<'ebook>> + 'ebook {
self.data()
.refinements
.by_refinements(opf::ALTERNATE_SCRIPT)
.map(|(_, script)| {
let code = script.language().unwrap_or_default();
AlternateScript::new(
&script.value,
LanguageTag::new(code, LanguageKind::Bcp47),
)
})
}
fn get_legacy_alt_script(&self) -> Option<AlternateScript<'ebook>> {
let attributes = &self.data().attributes;
let script = attributes.by_name(opf::OPF_ALT_REP)?.value();
let code = attributes.by_name(opf::OPF_ALT_REP_LANG)?.value();
Some(AlternateScript::new(
script,
LanguageTag::new(code, LanguageKind::Bcp47),
))
}
#[doc = doc::inherent!(MetaEntry, value)]
pub fn value(&self) -> &'ebook str {
&self.data().value
}
#[doc = doc::inherent!(MetaEntry, order)]
pub fn order(&self) -> usize {
self.index()
}
#[doc = doc::inherent!(MetaEntry, file_as)]
pub fn file_as(&self) -> Option<&'ebook str> {
self.data()
.refinements
.by_refinement(opf::FILE_AS)
.map(|refinement| refinement.value.as_str())
.or_else(|| {
self.data()
.attributes
.by_name(opf::OPF_FILE_AS)
.map(|attribute| attribute.value())
})
}
#[doc = doc::inherent!(MetaEntry, alternate_scripts)]
pub fn alternate_scripts(
&self,
) -> impl Iterator<Item = AlternateScript<'ebook>> + 'ebook {
let mut scripts = self.get_modern_alt_script().peekable();
let fallback = if scripts.peek().is_none() {
self.get_legacy_alt_script()
} else {
None
};
scripts.chain(fallback)
}
}
impl Sealed for $implementation<'_> {}
impl<'ebook> MetaEntry<'ebook> for $implementation<'ebook> {
fn value(&self) -> &'ebook str {
self.value()
}
fn order(&self) -> usize {
self.order()
}
fn file_as(&self) -> Option<&'ebook str> {
self.file_as()
}
fn alternate_scripts(
&self,
) -> impl Iterator<Item = AlternateScript<'ebook>> + 'ebook {
self.alternate_scripts()
}
}
impl Display for $implementation<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
f.write_str(&self.value())
}
}
};
}
pub(super) use {impl_meta_entry, impl_meta_entry_abstraction};
}