// The two scanners the link syntax shares between the block phase (reference
// definitions are stripped from paragraphs) and the inline phase (inline links,
// images, reference links).
///|
/// A link reference definition, kept by the label it can be looked up with.
priv struct Reference {
url : String
title : String
}
///|
/// Scan a link destination at `offset`: either `<...>`, or a run that stops at
/// whitespace, balancing parentheses. Returns the destination as written and
/// the number of code units consumed, or `-1` as the length when there is none.
fn scan_link_url(text : StringView, offset : Int32) -> (String, Int32) {
let n = text.length()
let mut i = offset
if cu(text, i) == 0x3C {
i = i + 1
while i < n {
let c = cu(text, i)
if c == 0x3E {
i = i + 1
break
} else if c == 0x5C {
i = i + 2
} else if c == 0x0A || c == 0x3C {
return ("", -1)
} else {
i = i + 1
}
}
if i >= n {
return ("", -1)
}
return (
text.clamped_view(start=offset + 1, end=i - 1).to_owned(),
i - offset,
)
}
let mut depth = 0
while i < n {
let c = cu(text, i)
if c == 0x5C && is_ascii_punct(cu(text, i + 1)) && i + 1 < n {
i = i + 2
} else if c == 0x28 {
depth = depth + 1
i = i + 1
if depth > 32 {
return ("", -1)
}
} else if c == 0x29 {
if depth == 0 {
break
}
depth = depth - 1
i = i + 1
} else if c == 0x20 ||
c == 0x09 ||
c == 0x0A ||
c == 0x0D ||
c == 0x0B ||
c == 0x0C {
if i == offset {
return ("", -1)
}
break
} else {
i = i + 1
}
}
if i >= n {
return ("", -1)
}
(text.clamped_view(start=offset, end=i).to_owned(), i - offset)
}
///|
/// Scan a link label at `pos`, which must be `[`. Returns the label between the
/// brackets and the length consumed, or `-1` when it is not closed in time.
fn scan_link_label(text : StringView, pos : Int32) -> (String, Int32) {
if cu(text, pos) != 0x5B {
return ("", -1)
}
let n = text.length()
let mut i = pos + 1
let mut length = 0
while i < n {
let c = cu(text, i)
if c == 0x5B || c == 0x5D {
break
}
if c == 0x5C {
i = i + 1
length = length + 1
if is_ascii_punct(cu(text, i)) {
i = i + 1
length = length + 1
}
} else {
i = i + 1
length = length + 1
}
if length > 999 {
return ("", -1)
}
}
if cu(text, i) == 0x5D {
return (
trim_view(text.clamped_view(start=pos + 1, end=i)).to_owned(),
i + 1 - pos,
)
}
("", -1)
}
///|
/// Parse one link reference definition at `pos` and add it to `refs`. Returns
/// the position after it, or `None` when there is no definition there.
fn parse_reference(
text : StringView,
pos : Int32,
refs : Map[String, Reference],
) -> Int32? {
let n = text.length()
let (label, label_len) = scan_link_label(text, pos)
if label_len < 0 || label.is_empty() {
return None
}
let mut i = pos + label_len
if cu(text, i) != 0x3A {
return None
}
i = i + 1
// `spnl`: spaces, at most one newline, then spaces again.
while is_space_or_tab(cu(text, i)) {
i = i + 1
}
if cu(text, i) == 0x0A {
i = i + 1
while is_space_or_tab(cu(text, i)) {
i = i + 1
}
}
let (url, url_len) = scan_link_url(text, i)
if url_len < 0 {
return None
}
i = i + url_len
// An optional title, which only counts if a space introduced it.
let before_title = i
let mut j = i
while is_space_or_tab(cu(text, j)) {
j = j + 1
}
if cu(text, j) == 0x0A {
j = j + 1
while is_space_or_tab(cu(text, j)) {
j = j + 1
}
}
let title_len = if j == before_title { 0 } else { scan_link_title(text, j) }
let mut title = ""
let mut end = before_title
if title_len > 0 {
title = text.clamped_view(start=j, end=j + title_len).to_owned()
end = j + title_len
}
// Nothing but spaces may follow, then the line has to end.
let mut k = end
while is_space_or_tab(cu(text, k)) {
k = k + 1
}
let mut stop = k
if !(cu(text, k) == 0x0A || cu(text, k) == 0x0D || k >= n) {
if title_len > 0 {
// The title may have run past the line; retry without it.
let mut m = before_title
while is_space_or_tab(cu(text, m)) {
m = m + 1
}
if !(cu(text, m) == 0x0A || cu(text, m) == 0x0D || m >= n) {
return None
}
title = ""
stop = m
} else {
return None
}
}
let key = normalize_label(label[:])
if !key.is_empty() && !refs.contains(key) {
refs[key] = { url: clean_url(url[:]), title: clean_title(title[:]), }
}
match cu(text, stop) {
0x0A => Some(stop + 1)
0x0D =>
if cu(text, stop + 1) == 0x0A {
Some(stop + 2)
} else {
Some(stop + 1)
}
_ => Some(stop)
}
}