use derivative::Derivative;
use derive_more::{Deref, DerefMut};
use downcast_rs::{impl_downcast, Downcast};
use std::collections::HashMap;
use std::fmt::Debug;
use crate::common::utils::normalize_reference;
use crate::generics::inline::full_link;
use crate::parser::block::{BlockRule, BlockState};
use crate::parser::extset::RootExt;
use crate::{MarkdownIt, Node, NodeValue};
#[derive(Debug, Deref, DerefMut)]
#[deref(forward)]
#[deref_mut(forward)]
pub struct ReferenceMap(Box<dyn CustomReferenceMap>);
impl ReferenceMap {
pub fn new(custom_map: impl CustomReferenceMap + 'static) -> Self {
Self(Box::new(custom_map))
}
}
impl Default for ReferenceMap {
fn default() -> Self {
Self::new(DefaultReferenceMap::new())
}
}
impl RootExt for ReferenceMap {}
pub trait CustomReferenceMap : Debug + Downcast + Send + Sync {
fn insert(&mut self, label: String, destination: String, title: Option<String>) -> bool;
fn get(&self, label: &str) -> Option<(&str, Option<&str>)>;
}
impl_downcast!(CustomReferenceMap);
#[derive(Default, Debug)]
pub struct DefaultReferenceMap(HashMap<ReferenceMapKey, ReferenceMapEntry>);
impl DefaultReferenceMap {
pub fn new() -> Self {
Self::default()
}
pub fn iter(&self) -> impl Iterator<Item = (&str, &str, Option<&str>)> {
Box::new(self.0.iter().map(|(a, b)| {
(a.label.as_str(), b.destination.as_str(), b.title.as_deref())
}))
}
}
impl CustomReferenceMap for DefaultReferenceMap {
fn insert(&mut self, label: String, destination: String, title: Option<String>) -> bool {
let Some(key) = ReferenceMapKey::new(label) else { return false; };
self.0.entry(key)
.or_insert(ReferenceMapEntry::new(destination, title));
true
}
fn get(&self, label: &str) -> Option<(&str, Option<&str>)> {
let key = ReferenceMapKey::new(label.to_owned())?;
self.0.get(&key)
.map(|r| (r.destination.as_str(), r.title.as_deref()))
}
}
#[derive(Derivative)]
#[derivative(Debug, Default, Hash, PartialEq, Eq)]
struct ReferenceMapKey {
#[derivative(PartialEq = "ignore")]
#[derivative(Hash = "ignore")]
pub label: String,
normalized: String,
}
impl ReferenceMapKey {
pub fn new(label: String) -> Option<Self> {
let normalized = normalize_reference(&label);
if normalized.is_empty() {
return None;
}
Some(Self { label, normalized })
}
}
#[derive(Debug, Default)]
struct ReferenceMapEntry {
pub destination: String,
pub title: Option<String>,
}
impl ReferenceMapEntry {
pub fn new(destination: String, title: Option<String>) -> Self {
Self { destination, title }
}
}
pub fn add(md: &mut MarkdownIt) {
md.block.add_rule::<ReferenceScanner>();
}
#[derive(Debug)]
pub struct Definition {
pub label: String,
pub destination: String,
pub title: Option<String>,
}
impl NodeValue for Definition {
fn render(&self, _: &Node, _: &mut dyn crate::Renderer) {}
}
#[doc(hidden)]
pub struct ReferenceScanner;
impl BlockRule for ReferenceScanner {
fn check(_: &mut BlockState) -> Option<()> {
None }
fn run(state: &mut BlockState) -> Option<(Node, usize)> {
if state.line_indent(state.line) >= state.md.max_indent { return None; }
let mut chars = state.get_line(state.line).chars();
let Some('[') = chars.next() else { return None; };
loop {
match chars.next() {
Some('\\') => { chars.next(); },
Some(']') => {
if let Some(':') = chars.next() {
break;
} else {
return None;
}
}
Some(_) => {},
None => break,
}
}
let start_line = state.line;
let mut next_line = start_line;
'outer: loop {
next_line += 1;
if next_line >= state.line_max || state.is_empty(next_line) { break; }
if state.line_indent(next_line) >= state.md.max_indent { continue; }
if state.line_offsets[next_line].indent_nonspace < 0 { continue; }
let old_state_line = state.line;
state.line = next_line;
if state.test_rules_at_line() {
state.line = old_state_line;
break 'outer;
}
state.line = old_state_line;
}
let (str_before_trim, _) = state.get_lines(start_line, next_line, state.blk_indent, false);
let str = str_before_trim.trim();
let mut chars = str.char_indices();
chars.next(); let label_end;
let mut lines = 0;
loop {
match chars.next() {
Some((_, '[')) => return None,
Some((p, ']')) => {
label_end = p;
break;
}
Some((_, '\n')) => lines += 1,
Some((_, '\\')) => {
if let Some((_, '\n')) = chars.next() {
lines += 1;
}
}
Some(_) => {},
None => return None,
}
}
let Some((_, ':')) = chars.next() else { return None; };
let mut pos = label_end + 2;
while let Some((_, ch @ (' ' | '\t' | '\n'))) = chars.next() {
if ch == '\n' { lines += 1; }
pos += 1;
}
let href;
if let Some(res) = full_link::parse_link_destination(str, pos, str.len()) {
if pos == res.pos { return None; }
href = state.md.link_formatter.normalize_link(&res.str);
state.md.link_formatter.validate_link(&href)?;
pos = res.pos;
lines += res.lines;
} else {
return None;
}
let dest_end_pos = pos;
let dest_end_lines = lines;
let start = pos;
let mut chars = str[pos..].chars();
while let Some(ch @ (' ' | '\t' | '\n')) = chars.next() {
if ch == '\n' { lines += 1; }
pos += 1;
}
let mut title = None;
if pos != start {
if let Some(res) = full_link::parse_link_title(str, pos, str.len()) {
title = Some(res.str);
pos = res.pos;
lines += res.lines;
} else {
pos = dest_end_pos;
lines = dest_end_lines;
}
}
let mut chars = str[pos..].chars();
loop {
match chars.next() {
Some(' ' | '\t') => pos += 1,
Some('\n') | None => break,
Some(_) if title.is_some() => {
title = None;
pos = dest_end_pos;
lines = dest_end_lines;
chars = str[pos..].chars();
}
Some(_) => {
return None;
}
}
}
let references = state.root_ext.get_or_insert_default::<ReferenceMap>();
if !references.insert(str[1..label_end].to_owned(), href.clone(), title.clone()) { return None; }
Some((Node::new(
Definition {
label: str[1..label_end].to_owned(),
destination: href,
title
}),
lines + 1
))
}
}