orbital_markdown/
citations.rs1use regex::Regex;
2
3use crate::citation_style::CitationLinkStyle;
4use crate::links::ORBITAL_LINK_INLINE_CLASS;
5
6#[derive(Clone, Debug, PartialEq, Eq)]
8pub struct CitationRef<'a> {
9 pub id: &'a str,
10 pub display_index: usize,
11}
12
13pub fn replace_citation_refs(
15 html: &str,
16 citations: &[CitationRef<'_>],
17 style: CitationLinkStyle,
18) -> String {
19 if citations.is_empty() {
20 return html.to_string();
21 }
22
23 let mut result = html.to_string();
24 for citation in citations {
25 let pattern = format!(r"\[\^{}\]", regex::escape(citation.id));
26 let Ok(re) = Regex::new(&pattern) else {
27 continue;
28 };
29 let row_anchor = format!("{}{}", style.row_anchor_prefix, citation.id);
30 let ref_id = format!("{}{}", style.ref_id_prefix, citation.id);
31 let replacement = format!(
32 r##"<sup class="{class}"><a href="#{row_anchor}" id="{ref_id}" class="{link_class}" data-citation-id="{id}">{index}</a></sup>"##,
33 class = style.anchor_class,
34 link_class = ORBITAL_LINK_INLINE_CLASS,
35 row_anchor = row_anchor,
36 ref_id = ref_id,
37 id = citation.id,
38 index = citation.display_index,
39 );
40 result = re.replace_all(&result, replacement.as_str()).to_string();
41 }
42 result
43}
44
45pub fn strip_duplicate_images(markdown: &str, attachment_urls: &[&str]) -> String {
47 if attachment_urls.is_empty() {
48 return markdown.to_string();
49 }
50
51 let mut result = markdown.to_string();
52 for url in attachment_urls {
53 let escaped = regex::escape(url);
54 let pattern = format!(r"!\[[^\]]*\]\({escaped}\)");
55 if let Ok(re) = Regex::new(&pattern) {
56 result = re.replace_all(&result, "").to_string();
57 }
58 }
59 result
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65
66 #[test]
67 fn replaces_discussion_citation_ref_in_html() {
68 let html = "<p>See [^cit-1] for details.</p>";
69 let citations = vec![CitationRef {
70 id: "cit-1",
71 display_index: 1,
72 }];
73 let out = replace_citation_refs(html, &citations, CitationLinkStyle::discussion());
74 assert!(out.contains("discussion-citation-row-cit-1"));
75 assert!(out.contains(">1</a>"));
76 assert!(!out.contains("[^cit-1]"));
77 }
78
79 #[test]
80 fn replaces_history_citation_ref_in_html() {
81 let html = "<p>See [^audit-1] for details.</p>";
82 let citations = vec![CitationRef {
83 id: "audit-1",
84 display_index: 2,
85 }];
86 let out = replace_citation_refs(html, &citations, CitationLinkStyle::history());
87 assert!(out.contains("history-citation-row-audit-1"));
88 assert!(out.contains("history-citation-ref-audit-1"));
89 assert!(out.contains("orbital-history__citation-ref"));
90 assert!(out.contains("orbital-link orbital-link--inline"));
91 }
92}