markdown_it/plugins/cmark/inline/
autolink.rs1use once_cell::sync::Lazy;
7use regex::Regex;
8
9use crate::parser::inline::{InlineRule, InlineState, TextSpecial};
10use crate::{MarkdownIt, Node, NodeValue, Renderer};
11
12#[derive(Debug)]
13pub struct Autolink {
14 pub url: String,
15}
16
17impl NodeValue for Autolink {
18 fn render(&self, node: &Node, fmt: &mut dyn Renderer) {
19 let mut attrs = node.attrs.clone();
20 attrs.push(("href", self.url.clone()));
21
22 fmt.open("a", &attrs);
23 fmt.contents(&node.children);
24 fmt.close("a");
25 }
26}
27
28pub fn add(md: &mut MarkdownIt) {
29 md.inline.add_rule::<AutolinkScanner>();
30}
31
32static AUTOLINK_RE : Lazy<Regex> = Lazy::new(|| {
33 Regex::new(r"^([a-zA-Z][a-zA-Z0-9+.\-]{1,31}):([^<>\x00-\x20]*)$").unwrap()
34});
35
36static EMAIL_RE : Lazy<Regex> = Lazy::new(|| {
37 Regex::new(r"^([a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)$").unwrap()
38});
39
40#[doc(hidden)]
41pub struct AutolinkScanner;
42impl InlineRule for AutolinkScanner {
43 const MARKER: char = '<';
44
45 fn run(state: &mut InlineState) -> Option<(Node, usize)> {
46 let mut chars = state.src[state.pos..state.pos_max].chars();
47 if chars.next().unwrap() != '<' { return None; }
48
49 let mut pos = state.pos + 2;
50
51 loop {
52 match chars.next() {
53 Some('<') | None => return None,
54 Some('>') => break,
55 Some(x) => pos += x.len_utf8(),
56 }
57 }
58
59 let url = &state.src[state.pos+1..pos-1];
60 let is_autolink = AUTOLINK_RE.is_match(url);
61 let is_email = EMAIL_RE.is_match(url);
62
63 if !is_autolink && !is_email { return None; }
64
65 let full_url = if is_autolink {
66 state.md.link_formatter.normalize_link(url)
67 } else {
68 state.md.link_formatter.normalize_link(&("mailto:".to_owned() + url))
69 };
70
71 state.md.link_formatter.validate_link(&full_url)?;
72
73 let content = state.md.link_formatter.normalize_link_text(url);
74
75 let mut inner_node = Node::new(TextSpecial {
76 content: content.clone(),
77 markup: content,
78 info: "autolink",
79 });
80 inner_node.srcmap = state.get_map(state.pos + 1, pos - 1);
81
82 let mut node = Node::new(Autolink { url: full_url });
83 node.children.push(inner_node);
84
85 Some((node, pos - state.pos))
86 }
87}