use std::collections::HashMap;
use derivative::Derivative;
use crate::{MarkdownIt, Node};
use crate::common::utils::normalize_reference;
use crate::generics::inline::full_link;
use crate::parser::block::{BlockRule, BlockState};
use crate::parser::extset::RootExt;
pub type ReferenceMap = HashMap<ReferenceMapKey, ReferenceMapEntry>;
impl RootExt for ReferenceMap {}
#[derive(Derivative)]
#[derivative(Debug, Default, Hash, PartialEq, Eq)]
pub struct ReferenceMapKey {
#[derivative(PartialEq = "ignore")]
#[derivative(Hash = "ignore")]
pub label: String,
normalized: String,
}
impl ReferenceMapKey {
pub fn new(label: String) -> Self {
let normalized = normalize_reference(&label);
Self { label, normalized }
}
}
#[derive(Debug, Default)]
pub 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>();
}
#[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) >= 4 { return None; }
let mut chars = state.get_line(state.line).chars();
if 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) >= 4 { 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,
}
}
if 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 label = normalize_reference(&str[1..label_end]);
if label.is_empty() {
return None;
}
let references = &mut state.root_ext.get_or_insert_default::<ReferenceMap>();
references.entry(ReferenceMapKey::new(label)).or_insert_with(|| ReferenceMapEntry::new(href, title));
Some((Node::default(), lines + 1))
}
}