#![allow(clippy::cast_precision_loss, clippy::cast_sign_loss, clippy::unused_self)]
use std::collections::BTreeMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "metadata", derive(serde::Serialize, serde::Deserialize))]
pub enum TextDirection {
#[cfg_attr(feature = "metadata", serde(rename = "ltr"))]
LeftToRight,
#[cfg_attr(feature = "metadata", serde(rename = "rtl"))]
RightToLeft,
#[cfg_attr(feature = "metadata", serde(rename = "auto"))]
Auto,
}
impl std::fmt::Display for TextDirection {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::LeftToRight => write!(f, "ltr"),
Self::RightToLeft => write!(f, "rtl"),
Self::Auto => write!(f, "auto"),
}
}
}
impl TextDirection {
#[must_use]
pub fn parse(s: &str) -> Option<Self> {
if s.eq_ignore_ascii_case("ltr") {
return Some(Self::LeftToRight);
}
if s.eq_ignore_ascii_case("rtl") {
return Some(Self::RightToLeft);
}
if s.eq_ignore_ascii_case("auto") {
return Some(Self::Auto);
}
None
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "metadata", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "metadata", serde(rename_all = "snake_case"))]
pub enum LinkType {
Anchor,
Internal,
External,
Email,
Phone,
Other,
}
impl std::fmt::Display for LinkType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Anchor => write!(f, "anchor"),
Self::Internal => write!(f, "internal"),
Self::External => write!(f, "external"),
Self::Email => write!(f, "email"),
Self::Phone => write!(f, "phone"),
Self::Other => write!(f, "other"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "metadata", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "metadata", serde(rename_all = "snake_case"))]
pub enum ImageType {
DataUri,
InlineSvg,
External,
Relative,
}
impl std::fmt::Display for ImageType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::DataUri => write!(f, "data_uri"),
Self::InlineSvg => write!(f, "inline_svg"),
Self::External => write!(f, "external"),
Self::Relative => write!(f, "relative"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "metadata", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "metadata", serde(rename_all = "snake_case"))]
pub enum StructuredDataType {
#[cfg_attr(feature = "metadata", serde(rename = "json_ld"))]
JsonLd,
Microdata,
#[cfg_attr(feature = "metadata", serde(rename = "rdfa"))]
RDFa,
}
impl std::fmt::Display for StructuredDataType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::JsonLd => write!(f, "json_ld"),
Self::Microdata => write!(f, "microdata"),
Self::RDFa => write!(f, "rdfa"),
}
}
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "metadata", derive(serde::Serialize, serde::Deserialize))]
pub struct DocumentMetadata {
pub title: Option<String>,
pub description: Option<String>,
pub keywords: Vec<String>,
pub author: Option<String>,
pub canonical_url: Option<String>,
pub base_href: Option<String>,
pub language: Option<String>,
pub text_direction: Option<TextDirection>,
pub open_graph: BTreeMap<String, String>,
pub twitter_card: BTreeMap<String, String>,
pub meta_tags: BTreeMap<String, String>,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "metadata", derive(serde::Serialize, serde::Deserialize))]
pub struct HeaderMetadata {
pub level: u8,
pub text: String,
pub id: Option<String>,
pub depth: usize,
pub html_offset: usize,
}
impl HeaderMetadata {
#[must_use]
pub const fn is_valid(&self) -> bool {
self.level >= 1 && self.level <= 6
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "metadata", derive(serde::Serialize, serde::Deserialize))]
pub struct LinkMetadata {
pub href: String,
pub text: String,
pub title: Option<String>,
pub link_type: LinkType,
pub rel: Vec<String>,
pub attributes: BTreeMap<String, String>,
}
impl LinkMetadata {
#[must_use]
pub fn classify_link(href: &str) -> LinkType {
if href.starts_with('#') {
LinkType::Anchor
} else if href.starts_with("mailto:") {
LinkType::Email
} else if href.starts_with("tel:") {
LinkType::Phone
} else if href.starts_with("http://") || href.starts_with("https://") {
LinkType::External
} else if href.starts_with('/') || href.starts_with("../") || href.starts_with("./") {
LinkType::Internal
} else {
LinkType::Other
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "metadata", derive(serde::Serialize, serde::Deserialize))]
pub struct ImageMetadata {
pub src: String,
pub alt: Option<String>,
pub title: Option<String>,
pub dimensions: Option<(u32, u32)>,
pub image_type: ImageType,
pub attributes: BTreeMap<String, String>,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "metadata", derive(serde::Serialize, serde::Deserialize))]
pub struct StructuredData {
pub data_type: StructuredDataType,
pub raw_json: String,
pub schema_type: Option<String>,
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "metadata", derive(serde::Serialize, serde::Deserialize))]
pub struct HtmlMetadata {
pub document: DocumentMetadata,
pub headers: Vec<HeaderMetadata>,
pub links: Vec<LinkMetadata>,
pub images: Vec<ImageMetadata>,
pub structured_data: Vec<StructuredData>,
}