#[cfg(feature = "write")]
mod write;
use crate::util::collection::{Keyed, KeyedVec};
use crate::util::str::StringExt;
use crate::util::uri;
use std::borrow::Cow;
use std::fmt::{Debug, Display};
use std::hash::Hash;
use std::ops::{Deref, DerefMut};
use std::str::SplitWhitespace;
#[cfg(feature = "write")]
pub use write::AttributesIterMut;
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub struct Href<'a>(&'a str);
impl<'a> Href<'a> {
pub(crate) fn new(href: &'a str) -> Self {
Self(href)
}
pub fn decode(&self) -> Cow<'a, str> {
uri::decode(self.0)
}
pub fn parent(&self) -> Option<Self> {
let parent = uri::parent(self.0);
(!parent.is_empty() && self.0 != parent).then_some(Self(parent))
}
pub fn extension(&self) -> Option<&'a str> {
uri::file_extension(self.0)
}
pub fn path(&self) -> Self {
Self(uri::path(self.0))
}
pub fn name(&self) -> Self {
Self(uri::filename(self.0))
}
pub fn fragment(&self) -> Option<&'a str> {
self.0.find('#').map(|index| &self.0[index + 1..])
}
pub fn query(&self) -> Option<&'a str> {
self.0
.find('?')
.and_then(|query| self.0[query + 1..].split('#').next())
}
pub fn as_str(&self) -> &'a str {
self.0
}
}
impl PartialEq<&str> for Href<'_> {
fn eq(&self, other: &&str) -> bool {
self.0 == *other
}
}
impl PartialEq<Href<'_>> for &str {
fn eq(&self, other: &Href<'_>) -> bool {
*self == other.0
}
}
impl<'a> AsRef<str> for Href<'a> {
fn as_ref(&self) -> &'a str {
self.0
}
}
impl Display for Href<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.0)
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct Properties(String);
impl Properties {
pub(crate) const EMPTY_REFERENCE: &'static Properties = &Properties(String::new());
pub fn len(&self) -> usize {
self.iter().count()
}
pub fn is_empty(&self) -> bool {
self.as_str().trim().is_empty()
}
pub fn get(&self, index: usize) -> Option<&str> {
self.iter().nth(index)
}
pub fn iter(&self) -> PropertiesIter<'_> {
PropertiesIter(self.0.split_whitespace())
}
pub fn has_property(&self, property: &str) -> bool {
self.iter().any(|value| value == property)
}
pub fn as_str(&self) -> &str {
self.0.trim()
}
}
impl Display for Properties {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl AsRef<str> for Properties {
fn as_ref(&self) -> &str {
&self.0
}
}
impl<'a> IntoIterator for &'a Properties {
type Item = &'a str;
type IntoIter = PropertiesIter<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
pub struct PropertiesIter<'a>(SplitWhitespace<'a>);
impl<'a> Iterator for PropertiesIter<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Attributes(KeyedVec<Attribute>);
impl Attributes {
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn get(&self, index: usize) -> Option<&Attribute> {
self.0.get(index)
}
pub fn iter(&self) -> AttributesIter<'_> {
AttributesIter(self.0.0.iter())
}
pub fn by_name(&self, name: &str) -> Option<&Attribute> {
self.0.by_key(name)
}
pub fn get_value(&self, name: &str) -> Option<&str> {
self.0.by_key(name).map(|attr| attr.value())
}
pub fn has_name(&self, name: &str) -> bool {
self.0.has_key(name)
}
}
impl<'a> IntoIterator for &'a Attributes {
type Item = &'a Attribute;
type IntoIter = AttributesIter<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
pub struct AttributesIter<'a>(std::slice::Iter<'a, Attribute>);
impl<'a> Iterator for AttributesIter<'a> {
type Item = &'a Attribute;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Attribute {
name: String,
value: Properties,
}
impl Attribute {
pub(crate) fn create(name: impl Into<String>, value: impl Into<String>) -> Self {
let mut name = name.into();
let mut value = value.into();
name.trim_in_place();
value.trim_in_place();
Self {
value: Properties(value),
name,
}
}
pub fn name(&self) -> Name<'_> {
Name(&self.name)
}
pub fn value(&self) -> &str {
self.value.as_str()
}
pub fn as_properties(&self) -> &Properties {
&self.value
}
}
impl Keyed for Attribute {
type Key = str;
fn key(&self) -> &Self::Key {
self.name.as_str()
}
}
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub struct Name<'a>(&'a str);
impl<'a> Name<'a> {
pub(crate) fn new(name: &'a str) -> Self {
Self(name)
}
fn index(&self) -> Option<usize> {
self.0.find(':')
}
pub fn prefix(&self) -> Option<&'a str> {
self.index().map(|i| &self.0[..i])
}
pub fn local(&self) -> &'a str {
self.index().map_or(self.0, |i| &self.0[i + 1..])
}
pub fn as_str(&self) -> &'a str {
self.0
}
}
impl PartialEq<&str> for Name<'_> {
fn eq(&self, other: &&str) -> bool {
self.0 == *other
}
}
impl PartialEq<Name<'_>> for &str {
fn eq(&self, other: &Name<'_>) -> bool {
*self == other.0
}
}
impl<'a> AsRef<str> for Name<'a> {
fn as_ref(&self) -> &'a str {
self.0
}
}
impl Display for Name<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.0)
}
}
#[derive(Copy, Clone, Debug, Default, Hash, PartialEq, Eq)]
pub enum TextDirection {
LeftToRight,
RightToLeft,
#[default]
Auto,
}
impl TextDirection {
const LEFT_TO_RIGHT: &'static str = "ltr";
const RIGHT_TO_LEFT: &'static str = "rtl";
const AUTO: &'static str = "auto";
pub fn is_ltr(self) -> bool {
matches!(self, Self::LeftToRight)
}
pub fn is_rtl(self) -> bool {
matches!(self, Self::RightToLeft)
}
pub fn is_auto(self) -> bool {
matches!(self, Self::Auto)
}
pub fn as_str(&self) -> &'static str {
match self {
Self::LeftToRight => Self::LEFT_TO_RIGHT,
Self::RightToLeft => Self::RIGHT_TO_LEFT,
Self::Auto => Self::AUTO,
}
}
}
impl Display for TextDirection {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl<A: AsRef<str>> From<A> for TextDirection {
fn from(value: A) -> Self {
match value.as_ref() {
Self::LEFT_TO_RIGHT => Self::LeftToRight,
Self::RIGHT_TO_LEFT => Self::RightToLeft,
_ => Self::Auto,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct PropertiesData(Properties);
impl Default for PropertiesData {
fn default() -> Self {
Self(Properties(String::new()))
}
}
impl Deref for PropertiesData {
type Target = Properties;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for PropertiesData {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl From<Option<String>> for PropertiesData {
fn from(value: Option<String>) -> Self {
Self(Properties(value.unwrap_or_default()))
}
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct AttributesData(Attributes);
impl Default for AttributesData {
fn default() -> Self {
Self(Attributes(KeyedVec(Vec::new())))
}
}
impl Deref for AttributesData {
type Target = Attributes;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for AttributesData {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl From<Vec<Attribute>> for AttributesData {
fn from(value: Vec<Attribute>) -> Self {
Self(Attributes(KeyedVec(value)))
}
}
#[cfg(test)]
mod tests {
use crate::ebook::element::Href;
#[test]
fn test_href_parent() {
let expected = [
(None, "abc.html"),
(Some("/EPUB"), "/EPUB/c1.xhtml"),
(Some("a"), "a/b"),
(None, ""),
(None, " "),
(None, "/"),
(None, "xyz"),
];
for (parent, href) in expected {
assert_eq!(parent, Href(href).parent().map(|p| p.as_str()))
}
}
#[test]
fn test_href_extension() {
let expected = [
(Some("xhtml"), "s04.xhtml#pgepubid00588"),
(Some("html"), "/EPUB/c1.html?q=1#start"),
(Some("css"), "abc.css"),
(Some("xyz"), ".xyz"),
(None, "a/b"),
(None, "abc"),
(None, ""),
(None, " "),
(None, "/"),
];
for (extension, href) in expected {
assert_eq!(extension, Href(href).extension())
}
}
#[test]
fn test_href_path() {
let expected = [
("s04.xhtml", "s04.xhtml#pgepubid00588"),
("/EPUB/c1.xhtml", "/EPUB/c1.xhtml?q=1#start"),
("a/b/c", "a/b/c"),
("", ""),
("/", "/"),
("", "?q=1#start"),
];
for (path, href) in expected {
assert_eq!(Href(path), Href(href).path())
}
}
#[test]
fn test_href_name() {
let expected = [
("s04.xhtml", "s04.xhtml#pgepubid00588"),
("c1.xhtml", "/EPUB/c1.xhtml?q=1#start"),
("c", "a/b/c"),
("", ""),
("", "/"),
("", "?q=1#start"),
];
for (path, href) in expected {
assert_eq!(Href(path), Href(href).name())
}
}
#[test]
fn test_href_fragment() {
let expected = [
(Some("page-epub-id00588"), "s04.xhtml#page-epub-id00588"),
(Some("hello%20world"), "/EPUB/c1.xhtml?q=1#hello%20world"),
(None, "a/b/c"),
(None, ""),
(None, "/"),
(Some(""), " #"),
(Some("start"), " #start"),
];
for (fragment, href) in expected {
assert_eq!(fragment, Href(href).fragment())
}
}
#[test]
fn test_href_query() {
let expected = [
(None, "s04.xhtml#pgepubid00588"),
(Some("q=1"), "/EPUB/c1.xhtml?q=1#hello%20world"),
(None, "a/b/c"),
(Some(""), "?#"),
(Some(""), "?"),
(Some("hello=world"), " ?hello=world"),
(None, " #start"),
];
for (query, href) in expected {
assert_eq!(query, Href(href).query())
}
}
}