use crate::md_elem::elem::*;
use crate::md_elem::MdContext;
use crate::select::match_selector::make_select_result;
use crate::select::string_matcher::StringMatcher;
use crate::select::{LinklikeMatcher, Result, Select, TrySelector};
#[derive(Debug, PartialEq)]
pub(crate) struct LinkSelector {
matchers: LinkMatchers,
}
impl From<LinklikeMatcher> for LinkSelector {
fn from(value: LinklikeMatcher) -> Self {
Self { matchers: value.into() }
}
}
impl TrySelector<Link> for LinkSelector {
fn try_select(&self, _: &MdContext, item: Link) -> Result<Select> {
match item {
Link::Standard(standard_link) => {
let original_link = Link::Standard(standard_link.clone());
let display_replaced = self
.matchers
.display_matcher
.match_replace_inlines(standard_link.display)
.map_err(|e| e.to_select_error("hyperlink"))?;
let url_replaced = self
.matchers
.url_matcher
.match_replace_string(standard_link.link.url)
.map_err(|e| e.to_select_error("hyperlink"))?;
let both_matched = display_replaced.matched_any && url_replaced.matched_any;
if both_matched {
let result = Link::Standard(StandardLink {
display: display_replaced.item,
link: LinkDefinition {
url: url_replaced.item,
title: standard_link.link.title,
reference: standard_link.link.reference,
},
});
Ok(Select::Hit(vec![result.into()]))
} else {
Ok(Select::Miss(original_link.into()))
}
}
Link::Autolink(autolink) => {
let original_link = Link::Autolink(autolink.clone());
let display_replaced = self
.matchers
.display_matcher
.match_replace_string(autolink.url.clone())
.map_err(|e| e.to_select_error("hyperlink"))?;
let url_replaced = self
.matchers
.url_matcher
.match_replace_string(autolink.url)
.map_err(|e| e.to_select_error("hyperlink"))?;
let both_matched = display_replaced.matched_any && url_replaced.matched_any;
let result = if both_matched {
Link::Autolink(Autolink {
url: url_replaced.item,
style: autolink.style,
})
} else {
original_link
};
Ok(make_select_result(result, both_matched))
}
}
}
}
#[derive(Debug, PartialEq)]
pub(crate) struct ImageSelector {
matchers: LinkMatchers,
}
impl From<LinklikeMatcher> for ImageSelector {
fn from(value: LinklikeMatcher) -> Self {
Self { matchers: value.into() }
}
}
impl TrySelector<Image> for ImageSelector {
fn try_select(&self, _: &MdContext, item: Image) -> Result<Select> {
let original_image = item.clone();
let alt_replaced = self
.matchers
.display_matcher
.match_replace_string(item.alt)
.map_err(|e| e.to_select_error("image"))?;
let url_replaced = self
.matchers
.url_matcher
.match_replace_string(item.link.url)
.map_err(|e| e.to_select_error("image"))?;
let both_matched = alt_replaced.matched_any && url_replaced.matched_any;
let result = if both_matched {
Image {
alt: alt_replaced.item,
link: LinkDefinition {
url: url_replaced.item,
title: item.link.title,
reference: item.link.reference,
},
}
} else {
original_image
};
Ok(make_select_result(result, both_matched))
}
}
#[derive(Debug, PartialEq)]
pub(crate) struct LinkMatchers {
pub(crate) display_matcher: StringMatcher,
pub(crate) url_matcher: StringMatcher,
}
impl From<LinklikeMatcher> for LinkMatchers {
fn from(value: LinklikeMatcher) -> Self {
Self {
display_matcher: value.display_matcher.into(),
url_matcher: value.url_matcher.into(),
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::md_elem::{mdq_inline, MdElem};
use crate::select::MatchReplace;
use crate::util::utils_for_test::unwrap;
#[test]
fn link_selector_url_replacement() {
let link_matcher = LinklikeMatcher {
display_matcher: MatchReplace::match_any(),
url_matcher: MatchReplace::build(|b| b.match_regex("original.com").replacement("newsite.com")),
};
let link = Link::Standard(StandardLink {
display: vec![],
link: LinkDefinition {
url: "https://original.com/path".to_string(),
title: None,
reference: LinkReference::Inline,
},
});
let link_selector = LinkSelector::from(link_matcher);
let result = link_selector.try_select(&MdContext::default(), link);
unwrap!(result, Ok(Select::Hit(elems)));
assert_eq!(elems.len(), 1);
unwrap!(&elems[0], MdElem::Inline(Inline::Link(modified_link)));
match modified_link {
Link::Standard(standard_link) => assert_eq!(standard_link.link.url, "https://newsite.com/path"),
Link::Autolink(..) => panic!("Expected Standard link, got Autolink"),
}
}
#[test]
fn image_selector_url_replacement() {
let image_matcher = LinklikeMatcher {
display_matcher: MatchReplace::match_any(),
url_matcher: MatchReplace::build(|b| b.match_regex("old-image.png").replacement("new-image.png")),
};
let image = Image {
alt: "alt text".to_string(),
link: LinkDefinition {
url: "https://example.com/old-image.png".to_string(),
title: None,
reference: LinkReference::Inline,
},
};
let image_selector = ImageSelector::from(image_matcher);
let result = image_selector.try_select(&MdContext::default(), image);
unwrap!(result, Ok(Select::Hit(elems)));
assert_eq!(elems.len(), 1);
unwrap!(&elems[0], MdElem::Inline(Inline::Image(modified_image)));
assert_eq!(modified_image.link.url, "https://example.com/new-image.png");
}
#[test]
fn image_url_replaced_but_alt_does_not_match() {
let image_matcher = LinklikeMatcher {
display_matcher: MatchReplace::build(|b| b.match_regex("^wrong alt text$")),
url_matcher: MatchReplace::build(|b| b.match_regex("old-image.png").replacement("new-image.png")),
};
let original_image = Image {
alt: "original alt text".to_string(),
link: LinkDefinition {
url: "https://example.com/old-image.png".to_string(),
title: None,
reference: LinkReference::Inline,
},
};
let image_selector = ImageSelector::from(image_matcher);
let result = image_selector.try_select(&MdContext::default(), original_image.clone());
unwrap!(result, Ok(Select::Miss(elem)));
unwrap!(&elem, MdElem::Inline(Inline::Image(result_image)));
assert_eq!(result_image, &original_image);
}
#[test]
fn link_url_replaced_but_display_does_not_match() {
let link_matcher = LinklikeMatcher {
display_matcher: MatchReplace::build(|b| b.match_regex("^wrong display text$")),
url_matcher: MatchReplace::build(|b| b.match_regex("original.com").replacement("newsite.com")),
};
let original_link = Link::Standard(StandardLink {
display: vec![mdq_inline!("original display text")],
link: LinkDefinition {
url: "https://original.com/path".to_string(),
title: None,
reference: LinkReference::Inline,
},
});
let link_selector = LinkSelector::from(link_matcher);
let result = link_selector.try_select(&MdContext::default(), original_link.clone());
unwrap!(result, Ok(Select::Miss(elem)));
unwrap!(&elem, MdElem::Inline(Inline::Link(result_link)));
assert_eq!(result_link, &original_link);
}
#[test]
fn link_display_text_replacement() {
let link_matcher = LinklikeMatcher {
display_matcher: MatchReplace::build(|b| b.match_regex("old text").replacement("new text")),
url_matcher: MatchReplace::match_any(),
};
let link = Link::Standard(StandardLink {
display: vec![mdq_inline!("old text here")],
link: LinkDefinition {
url: "https://example.com".to_string(),
title: None,
reference: LinkReference::Inline,
},
});
let link_selector = LinkSelector::from(link_matcher);
let result = link_selector.try_select(&MdContext::default(), link);
unwrap!(result, Ok(Select::Hit(elems)));
assert_eq!(elems.len(), 1);
unwrap!(&elems[0], MdElem::Inline(Inline::Link(Link::Standard(standard_link))));
assert_eq!(standard_link.display, vec![mdq_inline!("new text here")]);
assert_eq!(standard_link.link.url, "https://example.com");
}
#[test]
fn image_alt_text_replacement() {
let image_matcher = LinklikeMatcher {
display_matcher: MatchReplace::build(|b| b.match_regex("old alt").replacement("new alt")),
url_matcher: MatchReplace::match_any(),
};
let image = Image {
alt: "old alt text".to_string(),
link: LinkDefinition {
url: "https://example.com/image.png".to_string(),
title: None,
reference: LinkReference::Inline,
},
};
let image_selector = ImageSelector::from(image_matcher);
let result = image_selector.try_select(&MdContext::default(), image);
unwrap!(result, Ok(Select::Hit(elems)));
assert_eq!(elems.len(), 1);
unwrap!(&elems[0], MdElem::Inline(Inline::Image(modified_image)));
assert_eq!(modified_image.alt, "new alt text");
assert_eq!(modified_image.link.url, "https://example.com/image.png");
}
}