#[derive(Debug, Clone, Default)]
pub struct Attributes {
inner: Vec<(String, String)>,
}
impl Attributes {
#[inline]
pub fn new() -> Self {
Self { inner: Vec::new() }
}
#[inline]
pub fn with_capacity(cap: usize) -> Self {
Self { inner: Vec::with_capacity(cap) }
}
#[inline]
pub fn get(&self, key: &str) -> Option<&String> {
for (k, v) in &self.inner {
if k == key {
return Some(v);
}
}
None
}
#[inline]
pub fn contains_key(&self, key: &str) -> bool {
self.inner.iter().any(|(k, _)| k == key)
}
#[inline]
pub fn insert(&mut self, key: String, value: String) {
for (k, v) in &mut self.inner {
if *k == key {
*v = value;
return;
}
}
self.inner.push((key, value));
}
#[inline]
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
#[inline]
pub fn clear(&mut self) {
self.inner.clear();
}
#[inline]
pub fn iter(&self) -> std::slice::Iter<'_, (String, String)> {
self.inner.iter()
}
}
#[derive(Debug, Clone)]
pub struct TailwindData {
pub prefix: Option<String>,
pub suffix: Option<String>,
pub hidden: bool,
}
#[derive(Debug, Clone)]
pub struct ElementNode {
pub attributes: Attributes,
pub tailwind: Option<Box<TailwindData>>,
pub custom_name: Option<String>,
pub depth: usize,
pub index: usize,
pub current_walk_index: usize,
pub child_text_node_index: usize,
pub tag_id: Option<u8>,
pub contains_whitespace: bool,
pub excluded_from_markdown: bool,
pub is_inline: bool,
pub excludes_text_nodes: bool,
pub is_non_nesting: bool,
pub collapses_inner_white_space: bool,
pub spacing: Option<[u8; 2]>,
}
impl ElementNode {
#[inline]
pub fn name(&self) -> &str {
if let Some(ref n) = self.custom_name {
n.as_str()
} else if let Some(id) = self.tag_id {
crate::consts::TAG_NAMES[id as usize]
} else {
""
}
}
}
#[derive(Clone, Copy)]
pub struct TagHandler {
pub is_self_closing: bool,
pub is_non_nesting: bool,
pub collapses_inner_white_space: bool,
pub is_inline: bool,
pub spacing: Option<[u8; 2]>,
pub excludes_text_nodes: bool,
pub needs_attributes: bool,
}
#[derive(Debug, Clone, Default)]
pub struct FilterConfig {
pub include: Option<Vec<String>>,
pub exclude: Option<Vec<String>>,
pub process_children: Option<bool>,
}
impl FilterConfig {
pub fn exclude(selectors: &[&str]) -> Self {
Self {
exclude: Some(selectors.iter().map(ToString::to_string).collect()),
..Default::default()
}
}
pub fn include(selectors: &[&str]) -> Self {
Self {
include: Some(selectors.iter().map(ToString::to_string).collect()),
..Default::default()
}
}
}
#[derive(Debug, Clone, Default, Copy)]
pub struct IsolateMainConfig;
#[derive(Debug, Clone, Default, Copy)]
pub struct TailwindConfig;
#[derive(Debug, Clone, Default)]
pub struct FrontmatterConfig {
pub additional_fields: Option<Vec<(String, String)>>,
pub meta_fields: Option<Vec<String>>,
}
impl FrontmatterConfig {
pub fn with_meta_fields(fields: &[&str]) -> Self {
Self {
meta_fields: Some(fields.iter().map(ToString::to_string).collect()),
..Default::default()
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ExtractionConfig {
pub selectors: Vec<String>,
}
impl ExtractionConfig {
pub fn new(selectors: &[&str]) -> Self {
Self {
selectors: selectors.iter().map(ToString::to_string).collect(),
}
}
}
#[derive(Debug, Clone)]
pub enum ParsedSelector {
Tag(String),
Class(String),
Id(String),
Attribute { name: String, operator: Option<String>, value: Option<String> },
Compound(Vec<Self>),
}
#[derive(Debug, Clone)]
pub struct ExtractedElement {
pub selector: String,
pub tag_name: String,
pub text_content: String,
pub attributes: Vec<(String, String)>,
}
#[derive(Debug, Clone, Default)]
pub struct TagOverrideConfig {
pub enter: Option<String>,
pub exit: Option<String>,
pub spacing: Option<[u8; 2]>,
pub is_inline: Option<bool>,
pub is_self_closing: Option<bool>,
pub collapses_inner_white_space: Option<bool>,
pub alias_tag_id: Option<u8>,
}
impl TagOverrideConfig {
pub fn alias(tag_name: &str) -> Self {
Self {
alias_tag_id: crate::consts::get_tag_id(tag_name),
..Default::default()
}
}
pub fn wrap(enter: impl Into<String>, exit: impl Into<String>) -> Self {
Self {
enter: Some(enter.into()),
exit: Some(exit.into()),
is_inline: Some(true),
..Default::default()
}
}
}
#[derive(Debug, Clone, Default)]
pub struct PluginConfig {
pub filter: Option<FilterConfig>,
pub isolate_main: Option<IsolateMainConfig>,
pub frontmatter: Option<FrontmatterConfig>,
pub tailwind: Option<TailwindConfig>,
pub extraction: Option<ExtractionConfig>,
pub tag_overrides: Option<Vec<(String, TagOverrideConfig)>>,
}
impl PluginConfig {
pub fn isolate_main() -> Self {
Self {
isolate_main: Some(IsolateMainConfig),
..Default::default()
}
}
pub fn frontmatter() -> Self {
Self {
frontmatter: Some(FrontmatterConfig::default()),
..Default::default()
}
}
#[must_use]
pub fn with_tag_override(mut self, tag: impl Into<String>, config: TagOverrideConfig) -> Self {
let tag = tag.into().to_ascii_lowercase();
let overrides = self.tag_overrides.get_or_insert_with(Vec::new);
if let Some((_, existing)) = overrides.iter_mut().find(|(t, _)| *t == tag) {
*existing = config;
} else {
overrides.push((tag, config));
}
self
}
}
#[derive(Debug, Clone, Default)]
pub struct CleanConfig {
pub urls: bool,
pub fragments: bool,
pub empty_links: bool,
pub blank_lines: bool,
pub redundant_links: bool,
pub self_link_headings: bool,
pub empty_images: bool,
pub empty_link_text: bool,
}
impl CleanConfig {
pub fn all() -> Self {
Self {
urls: true,
fragments: true,
empty_links: true,
blank_lines: true,
redundant_links: true,
self_link_headings: true,
empty_images: true,
empty_link_text: true,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct HTMLToMarkdownOptions {
pub origin: Option<String>,
pub clean_urls: bool,
pub clean: Option<CleanConfig>,
pub plugins: Option<PluginConfig>,
pub wrap_width: Option<usize>,
}
impl HTMLToMarkdownOptions {
#[must_use]
pub fn with_origin(mut self, origin: impl Into<String>) -> Self {
self.origin = Some(origin.into());
self
}
#[must_use]
pub fn with_clean_urls(mut self) -> Self {
self.clean_urls = true;
self
}
#[must_use]
pub fn with_clean(mut self, clean: CleanConfig) -> Self {
self.clean = Some(clean);
self
}
#[must_use]
pub fn with_plugins(mut self, plugins: PluginConfig) -> Self {
self.plugins = Some(plugins);
self
}
#[must_use]
pub fn with_wrap_width(mut self, width: usize) -> Self {
self.wrap_width = Some(width);
self
}
}
pub struct MdreamResult {
pub markdown: String,
pub extracted: Option<Vec<ExtractedElement>>,
pub frontmatter: Option<Vec<(String, String)>>,
}