oxihuman_export/
endnote_export.rs1#![allow(dead_code)]
4
5#[derive(Debug, Clone, PartialEq)]
9pub enum EndnoteRefType {
10 JournalArticle,
11 Book,
12 BookSection,
13 ConferencePaper,
14 Report,
15 Thesis,
16 WebPage,
17}
18
19impl EndnoteRefType {
20 pub fn label(&self) -> &'static str {
22 match self {
23 Self::JournalArticle => "Journal Article",
24 Self::Book => "Book",
25 Self::BookSection => "Book Section",
26 Self::ConferencePaper => "Conference Paper",
27 Self::Report => "Report",
28 Self::Thesis => "Thesis",
29 Self::WebPage => "Web Page",
30 }
31 }
32}
33
34#[derive(Debug, Clone)]
36pub struct EndnoteRef {
37 pub ref_type: EndnoteRefType,
38 pub title: String,
39 pub authors: Vec<String>,
40 pub year: Option<u32>,
41 pub journal: Option<String>,
42 pub volume: Option<String>,
43 pub pages: Option<String>,
44 pub doi: Option<String>,
45}
46
47impl EndnoteRef {
48 pub fn new(ref_type: EndnoteRefType, title: impl Into<String>) -> Self {
50 Self {
51 ref_type,
52 title: title.into(),
53 authors: Vec::new(),
54 year: None,
55 journal: None,
56 volume: None,
57 pages: None,
58 doi: None,
59 }
60 }
61
62 pub fn add_author(&mut self, author: impl Into<String>) {
64 self.authors.push(author.into());
65 }
66}
67
68#[derive(Debug, Clone, Default)]
70pub struct EndnoteLibrary {
71 pub refs: Vec<EndnoteRef>,
72}
73
74impl EndnoteLibrary {
75 pub fn add_ref(&mut self, r: EndnoteRef) {
77 self.refs.push(r);
78 }
79
80 pub fn ref_count(&self) -> usize {
82 self.refs.len()
83 }
84}
85
86pub fn render_ref_xml(r: &EndnoteRef) -> String {
88 let mut out = format!(
89 " <record>\n <ref-type name=\"{}\"/>\n",
90 r.ref_type.label()
91 );
92 out.push_str(&format!(" <title>{}</title>\n", xml_escape(&r.title)));
93 for author in &r.authors {
94 out.push_str(&format!(" <author>{}</author>\n", xml_escape(author)));
95 }
96 if let Some(y) = r.year {
97 out.push_str(&format!(" <year>{y}</year>\n"));
98 }
99 if let Some(doi) = &r.doi {
100 out.push_str(&format!(
101 " <electronic-resource-num>{doi}</electronic-resource-num>\n"
102 ));
103 }
104 out.push_str(" </record>\n");
105 out
106}
107
108pub fn render_endnote_xml(lib: &EndnoteLibrary) -> String {
110 let mut out = String::from("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<xml>\n <records>\n");
111 for r in &lib.refs {
112 out.push_str(&render_ref_xml(r));
113 }
114 out.push_str(" </records>\n</xml>\n");
115 out
116}
117
118pub fn validate_ref(r: &EndnoteRef) -> bool {
120 !r.title.is_empty()
121}
122
123pub fn count_by_type(lib: &EndnoteLibrary, ref_type: &EndnoteRefType) -> usize {
125 lib.refs.iter().filter(|r| &r.ref_type == ref_type).count()
126}
127
128fn xml_escape(s: &str) -> String {
129 s.replace('&', "&")
130 .replace('<', "<")
131 .replace('>', ">")
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 fn sample_ref() -> EndnoteRef {
139 let mut r = EndnoteRef::new(EndnoteRefType::JournalArticle, "Test Paper");
140 r.add_author("Smith, J.");
141 r.year = Some(2026);
142 r.doi = Some("10.1234/test".into());
143 r
144 }
145
146 #[test]
147 fn ref_type_label() {
148 assert_eq!(EndnoteRefType::JournalArticle.label(), "Journal Article");
149 }
150
151 #[test]
152 fn ref_count() {
153 let mut lib = EndnoteLibrary::default();
154 lib.add_ref(sample_ref());
155 assert_eq!(lib.ref_count(), 1);
156 }
157
158 #[test]
159 fn render_xml_starts_correctly() {
160 let mut lib = EndnoteLibrary::default();
161 lib.add_ref(sample_ref());
162 let s = render_endnote_xml(&lib);
163 assert!(s.starts_with("<?xml"));
164 }
165
166 #[test]
167 fn render_contains_title() {
168 let s = render_ref_xml(&sample_ref());
169 assert!(s.contains("Test Paper"));
170 }
171
172 #[test]
173 fn render_contains_author() {
174 let s = render_ref_xml(&sample_ref());
175 assert!(s.contains("Smith"));
176 }
177
178 #[test]
179 fn render_contains_doi() {
180 let s = render_ref_xml(&sample_ref());
181 assert!(s.contains("10.1234/test"));
182 }
183
184 #[test]
185 fn validate_ok() {
186 assert!(validate_ref(&sample_ref()));
187 }
188
189 #[test]
190 fn validate_empty_title() {
191 let r = EndnoteRef::new(EndnoteRefType::Book, "");
192 assert!(!validate_ref(&r));
193 }
194
195 #[test]
196 fn count_by_type_correct() {
197 let mut lib = EndnoteLibrary::default();
198 lib.add_ref(sample_ref());
199 lib.add_ref(EndnoteRef::new(EndnoteRefType::Book, "A Book"));
200 assert_eq!(count_by_type(&lib, &EndnoteRefType::JournalArticle), 1);
201 }
202
203 #[test]
204 fn xml_escape_works() {
205 let s = render_ref_xml(&EndnoteRef::new(EndnoteRefType::WebPage, "A & B"));
207 assert!(s.contains("&"));
208 }
209}