///|
/// Parse Markdown citation blocks of the form:
/// > 原文:“quoted text”
/// > 来源:[title](https://example.org/page)
/// Inline-code backticks are Markdown formatting, not part of rendered quotes.
pub fn parse_markdown_report(input : String) -> CitationNotes {
let notes : Array[CitationNote] = []
let diagnostics : Array[Diagnostic] = []
let mut heading = "Markdown report"
let mut pending_quote : String? = None
let mut pending_line = 0
let mut in_code_block = false
let lines = input.split("\n").to_array()
for index = 0; index < lines.length(); index = index + 1 {
let line_number = index + 1
let line = lines[index].trim()
if line.has_prefix("```") || line.has_prefix("~~~") {
in_code_block = !in_code_block
continue
}
if in_code_block {
continue
}
if line.has_prefix("#") {
if pending_quote is Some(_) {
add_diagnostic(
diagnostics, pending_line, "MISSING_REPORT_SOURCE", "quote has no following source",
)
pending_quote = None
}
heading = match line.split_once(" ") {
Some((_, title)) => title.trim().to_owned()
None => line.to_owned()
}
continue
}
match report_quote_line(line) {
Some(quote) => {
if pending_quote is Some(_) {
add_diagnostic(
diagnostics, pending_line, "MISSING_REPORT_SOURCE", "quote has no following source",
)
}
if quote == "" {
add_diagnostic(
diagnostics, line_number, "EMPTY_REPORT_QUOTE", "quote is empty",
)
pending_quote = None
} else {
pending_quote = Some(quote)
pending_line = line_number
}
continue
}
None => ()
}
match report_source_line(line) {
Some(url) => {
match pending_quote {
Some(quote) =>
if url == "" {
add_diagnostic(
diagnostics, line_number, "INVALID_REPORT_SOURCE", "source needs an HTTP URL",
)
} else {
notes.push({ claim: heading, quote, url, line: pending_line, })
}
None =>
add_diagnostic(
diagnostics, line_number, "SOURCE_WITHOUT_QUOTE", "source has no preceding quote",
)
}
pending_quote = None
continue
}
None => ()
}
if pending_quote is Some(_) && line != "" && line != ">" {
add_diagnostic(
diagnostics, pending_line, "MISSING_REPORT_SOURCE", "quote has no following source",
)
pending_quote = None
}
}
if pending_quote is Some(_) {
add_diagnostic(
diagnostics, pending_line, "MISSING_REPORT_SOURCE", "quote has no following source",
)
}
if notes.is_empty() && diagnostics.is_empty() {
add_diagnostic(
diagnostics, 1, "NO_REPORT_CITATIONS", "no quote/source blocks found",
)
}
{ notes, diagnostics, }
}
///|
fn report_quote_line(line : StringView) -> String? {
let content = match line.strip_prefix(">") {
Some(rest) => rest.trim()
None => return None
}
let raw = match content.strip_prefix("原文:") {
Some(rest) => rest
None =>
match content.strip_prefix("引文:") {
Some(rest) => rest
None =>
match content.strip_prefix("Quote:") {
Some(rest) => rest
None => return None
}
}
}
let raw = raw.trim()
let unquoted = match raw.strip_prefix("“") {
Some(rest) => rest.strip_suffix("”").unwrap_or(rest)
None =>
match raw.strip_prefix("\"") {
Some(rest) => rest.strip_suffix("\"").unwrap_or(rest)
None => raw
}
}
let output = StringBuilder()
for char in unquoted.iter() {
if char != '`' {
output.write_char(char)
}
}
output.to_string().trim().to_owned() |> Some
}
///|
fn report_source_line(line : StringView) -> String? {
let content = match line.strip_prefix(">") {
Some(rest) => rest.trim()
None => return None
}
let raw = match content.strip_prefix("来源:") {
Some(rest) => rest.trim()
None =>
match content.strip_prefix("Source:") {
Some(rest) => rest.trim()
None => return None
}
}
let url = if raw.has_prefix("[") {
match raw.split_once("](") {
Some((_, tail)) =>
match tail.split_once(")") {
Some((url, _)) => url.trim().to_owned()
None => ""
}
None => ""
}
} else {
raw.to_owned()
}
if url.has_prefix("https://") || url.has_prefix("http://") {
Some(url)
} else {
Some("")
}
}