1 // Ported from: https://github.com/michel-kraemer/citeproc-java/tree/master/citeproc-java/templates
2 // Michel Kraemer
3 // Apache License 2.0
4 use citeproc_io::{unicode::is_latin_cyrillic, Date, DateOrRange, Name, NumberLike, Reference};
5 use csl::*;
6 use fnv::FnvHashMap;
7 use serde::{Deserialize, Serialize};
8
9 #[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
10 #[serde(rename_all = "UPPERCASE")]
11 pub enum RisType {
12 Abst,
13 Advs,
14 Aggr,
15 Ancient,
16 Art,
17 Bill,
18 Blog,
19 Book,
20 Case,
21 Chap,
22 Chart,
23 Clswk,
24 Comp,
25 Conf,
26 Cpaper,
27 Ctlg,
28 Data,
29 Dbase,
30 Dict,
31 Ebook,
32 Echap,
33 Edbook,
34 Ejour,
35 Elec,
36 Encyc,
37 Equa,
38 Figure,
39 Gen,
40 Govdoc,
41 Grant,
42 Hear,
43 Icomm,
44 Inpr,
45 Jfull,
46 Jour,
47 Legal,
48 Manscpt,
49 Map,
50 Mgzn,
51 Mpct,
52 Multi,
53 Music,
54 News,
55 Pamp,
56 Pat,
57 Pcomm,
58 Rprt,
59 Ser,
60 Slide,
61 Sound,
62 Stand,
63 Stat,
64 Std,
65 Thes,
66 Unpb,
67 Video,
68 }
69
70 impl RisType {
parse(ty: &str) -> Option<Self>71 pub fn parse(ty: &str) -> Option<Self> {
72 let ty = format!("\"{}\"", ty);
73 serde_json::from_str(&ty).ok()
74 }
75
csl(self) -> CslType76 pub fn csl(self) -> CslType {
77 match self {
78 Self::Abst => CslType::Article,
79 Self::Advs => CslType::Article,
80 Self::Aggr => CslType::Dataset,
81 Self::Ancient => CslType::Article,
82 Self::Art => CslType::Article,
83 Self::Bill => CslType::Bill,
84 Self::Blog => CslType::Webpage,
85 Self::Book => CslType::Book,
86 Self::Case => CslType::LegalCase,
87 Self::Chap => CslType::Chapter,
88 Self::Chart => CslType::Article,
89 Self::Clswk => CslType::Article,
90 Self::Comp => CslType::Article,
91 Self::Conf => CslType::PaperConference,
92 Self::Cpaper => CslType::PaperConference,
93 Self::Ctlg => CslType::Book,
94 Self::Data => CslType::Dataset,
95 Self::Dbase => CslType::Dataset,
96 Self::Dict => CslType::EntryDictionary,
97 Self::Ebook => CslType::Book,
98 Self::Echap => CslType::Chapter,
99 Self::Edbook => CslType::Book,
100 Self::Ejour => CslType::ArticleJournal,
101 Self::Elec => CslType::Article,
102 Self::Encyc => CslType::EntryEncyclopedia,
103 Self::Equa => CslType::Article,
104 Self::Figure => CslType::Figure,
105 Self::Gen => CslType::Article,
106 Self::Govdoc => CslType::Legislation,
107 Self::Grant => CslType::Legislation,
108 Self::Hear => CslType::Article,
109 Self::Icomm => CslType::PersonalCommunication,
110 Self::Inpr => CslType::PaperConference,
111 Self::Jfull => CslType::ArticleJournal,
112 Self::Jour => CslType::ArticleJournal,
113 Self::Legal => CslType::Legislation,
114 Self::Manscpt => CslType::Manuscript,
115 Self::Map => CslType::Map,
116 Self::Mgzn => CslType::ArticleMagazine,
117 Self::Mpct => CslType::MotionPicture,
118 Self::Multi => CslType::Webpage,
119 Self::Music => CslType::Song,
120 Self::News => CslType::ArticleNewspaper,
121 Self::Pamp => CslType::Pamphlet,
122 Self::Pat => CslType::Patent,
123 Self::Pcomm => CslType::PersonalCommunication,
124 Self::Rprt => CslType::Report,
125 Self::Ser => CslType::Article,
126 Self::Slide => CslType::Article,
127 Self::Sound => CslType::Song,
128 Self::Stand => CslType::Article,
129 Self::Stat => CslType::Legislation,
130 Self::Std => CslType::Article,
131 Self::Thes => CslType::Thesis,
132 Self::Unpb => CslType::Article,
133 Self::Video => CslType::MotionPicture,
134 }
135 }
136 }
137
138 #[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)]
139 pub struct RisReference {
140 pub id: Option<String>,
141 pub ty: Option<RisType>,
142 pub editors: Vec<String>,
143 pub tertiary_authors: Vec<String>,
144 pub subsidiary_authors: Vec<String>,
145 pub abstrct: Option<String>,
146 pub author_address: Option<String>,
147 pub accession_number: Option<String>,
148 pub authors: Vec<String>,
149 pub book_or_conference: Option<String>,
150 pub custom1: Option<String>,
151 pub custom2: Option<String>,
152 pub custom3: Option<String>,
153 pub custom4: Option<String>,
154 pub custom5: Option<String>,
155 pub custom6: Option<String>,
156 pub custom7: Option<String>,
157 pub custom8: Option<String>,
158 pub caption: Option<String>,
159 pub call_number: Option<String>,
160 pub place: Option<String>,
161 pub date: Option<String>,
162 pub name_of_database: Option<String>,
163 pub doi: Option<String>,
164 pub database_provider: Option<String>,
165 pub end_page: Option<String>,
166 pub edition: Option<String>,
167 pub issue: Option<String>,
168 pub journal: Option<String>,
169 pub keywords: Vec<String>,
170 pub file_attachments: Vec<String>,
171 pub figure: Option<String>,
172 pub language: Option<String>,
173 pub label: Option<String>,
174 pub number: Option<String>,
175 pub type_of_work: Option<String>,
176 pub notes: Vec<String>,
177 pub number_of_volumes: Option<String>,
178 pub original_publication: Option<String>,
179 pub publisher: Option<String>,
180 pub year: Option<String>,
181 pub reviewed_item: Option<String>,
182 pub research_notes: Option<String>,
183 pub reprint_edition: Option<String>,
184 pub section: Option<String>,
185 pub isbn_or_issn: Option<String>,
186 pub start_page: Option<String>,
187 pub short_title: Option<String>,
188 pub primary_title: Option<String>,
189 pub secondardy_title: Option<String>,
190 pub tertiary_title: Option<String>,
191 pub translated_authors: Vec<String>,
192 pub title: Option<String>,
193 pub translated_title: Option<String>,
194 pub url: Option<String>,
195 pub volume: Option<String>,
196 pub access_date: Option<String>,
197 }
198
199 #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
200 pub struct RisLibrary {
201 pub references: Vec<RisReference>,
202 }
203
204 impl RisLibrary {
parse<'a, I: Iterator<Item = &'a str>>(lines: I) -> Self205 pub fn parse<'a, I: Iterator<Item = &'a str>>(lines: I) -> Self {
206 let mut library = Self {
207 references: Vec::new(),
208 };
209
210 let mut reference: RisReference = RisReference::default();
211 for line in lines {
212 let line = line.trim();
213 if line == "" {
214 continue;
215 }
216
217 if line == "ER -" {
218 library.references.push(reference);
219 reference = RisReference::default();
220 continue;
221 }
222
223 let chars: Vec<_> = line.chars().collect();
224 if chars.len() < 7 || chars[4] != '-' {
225 continue;
226 }
227
228 let key: String = (&chars[..2]).iter().collect();
229 let value: String = (&chars[6..]).iter().collect();
230 match key.to_uppercase().as_str() {
231 "TY" => reference.ty = RisType::parse(&value),
232 "A2" => reference.editors.push(value),
233 "A3" => reference.tertiary_authors.push(value),
234 "A4" => reference.subsidiary_authors.push(value),
235 "AB" => reference.abstrct = Some(value),
236 "AD" => reference.author_address = Some(value),
237 "AN" => reference.accession_number = Some(value),
238 "AU" => reference.authors.push(value),
239 "BT" => reference.book_or_conference = Some(value),
240 "C1" => reference.custom1 = Some(value),
241 "C2" => reference.custom2 = Some(value),
242 "C3" => reference.custom3 = Some(value),
243 "C4" => reference.custom4 = Some(value),
244 "C5" => reference.custom5 = Some(value),
245 "C6" => reference.custom6 = Some(value),
246 "C7" => reference.custom7 = Some(value),
247 "C8" => reference.custom8 = Some(value),
248 "CA" => reference.caption = Some(value),
249 "CN" => reference.call_number = Some(value),
250 "CY" => reference.place = Some(value),
251 "DA" => reference.date = Some(value),
252 "DB" => reference.name_of_database = Some(value),
253 "DO" => reference.doi = Some(value),
254 "DP" => reference.database_provider = Some(value),
255 "ED" => reference.editors.push(value),
256 "EP" => reference.end_page = Some(value),
257 "ET" => reference.edition = Some(value),
258 "ID" => reference.id = Some(value),
259 "IS" => reference.issue = Some(value),
260 "JO" => reference.journal = Some(value),
261 "J2" => reference.journal = Some(value),
262 "KW" => reference.keywords.push(value),
263 "L1" => reference.file_attachments.push(value),
264 "L4" => reference.figure = Some(value),
265 "LA" => reference.language = Some(value),
266 "LB" => reference.label = Some(value),
267 "M1" => reference.number = Some(value),
268 "M3" => reference.type_of_work = Some(value),
269 "N1" => reference.notes.push(value),
270 "N2" => reference.abstrct = Some(value),
271 "NV" => reference.number_of_volumes = Some(value),
272 "OP" => reference.original_publication = Some(value),
273 "PB" => reference.publisher = Some(value),
274 "PY" => reference.year = Some(value),
275 "RI" => reference.reviewed_item = Some(value),
276 "RN" => reference.research_notes = Some(value),
277 "RP" => reference.reprint_edition = Some(value),
278 "SE" => reference.section = Some(value),
279 "SN" => reference.isbn_or_issn = Some(value),
280 "SP" => reference.start_page = Some(value),
281 "ST" => reference.short_title = Some(value),
282 "T1" => reference.primary_title = Some(value),
283 "T2" => reference.secondardy_title = Some(value),
284 "T3" => reference.tertiary_title = Some(value),
285 "TA" => reference.translated_authors.push(value),
286 "TI" => reference.title = Some(value),
287 "TT" => reference.translated_title = Some(value),
288 "U1" => reference.type_of_work = Some(value),
289 "UR" => reference.url = Some(value),
290 "VL" => reference.volume = Some(value),
291 "Y2" => reference.access_date = Some(value),
292 _ => (),
293 }
294 }
295 library
296 }
297 }
298
299 impl Into<Reference> for RisReference {
into(self) -> Reference300 fn into(self) -> Reference {
301 let csl_type = self.ty.expect("RIS type is missing").csl();
302 let mut date: FnvHashMap<DateVariable, DateOrRange> = FnvHashMap::default();
303 let mut name: FnvHashMap<NameVariable, Vec<Name>> = FnvHashMap::default();
304 let mut number: FnvHashMap<NumberVariable, NumberLike> = FnvHashMap::default();
305 let mut ordinary: FnvHashMap<Variable, String> = FnvHashMap::default();
306
307 if let Some(access_date) = self.access_date {
308 date.insert(DateVariable::Accessed, parse_date_or_range(access_date));
309 }
310
311 name.insert(NameVariable::Author, parse_authors(self.authors));
312 name.insert(NameVariable::Editor, parse_authors(self.editors));
313
314 if let Some(container_title) = self
315 .journal
316 .or(self.name_of_database)
317 .or(self.book_or_conference)
318 {
319 ordinary.insert(Variable::ContainerTitle, container_title.clone());
320 ordinary.insert(Variable::CollectionTitle, container_title);
321 }
322
323 if let Some(value) = self.date.or(self.year) {
324 let value = parse_date_or_range(value);
325 date.insert(DateVariable::Issued, value.clone());
326 date.insert(DateVariable::EventDate, value);
327 }
328
329 if let Some(url) = self.url {
330 ordinary.insert(Variable::URL, url);
331 }
332
333 let notes = self.notes;
334 ordinary.insert(
335 Variable::Note,
336 self.research_notes.unwrap_or_else(|| notes.join("\n")),
337 );
338
339 if let Some(issue) = self.issue {
340 number.insert(NumberVariable::Issue, parse_number(issue));
341 }
342
343 if let Some(num) = self.number {
344 number.insert(NumberVariable::Number, parse_number(num));
345 }
346
347 if let Some(place) = self.place {
348 ordinary.insert(Variable::EventPlace, place.clone());
349 ordinary.insert(Variable::PublisherPlace, place);
350 }
351
352 if let Some(abstrct) = self.abstrct {
353 ordinary.insert(Variable::Abstract, abstrct);
354 }
355
356 if let Some(call_number) = self.call_number {
357 ordinary.insert(Variable::CallNumber, call_number);
358 }
359
360 if let Some(doi) = self.doi {
361 ordinary.insert(Variable::DOI, doi);
362 }
363
364 if let Some(edition) = self.edition {
365 number.insert(NumberVariable::Edition, parse_number(edition));
366 }
367
368 if let Some(isbn_or_issn) = self.isbn_or_issn {
369 ordinary.insert(Variable::ISBN, isbn_or_issn.clone());
370 ordinary.insert(Variable::ISSN, isbn_or_issn);
371 }
372
373 ordinary.insert(Variable::Keyword, self.keywords.join(", "));
374
375 if let Some(number_of_volumes) = self.number_of_volumes {
376 number.insert(
377 NumberVariable::NumberOfVolumes,
378 parse_number(number_of_volumes),
379 );
380 }
381
382 if let Some(original_publication) = self.original_publication {
383 ordinary.insert(Variable::OriginalTitle, original_publication);
384 }
385
386 match (self.start_page, self.end_page) {
387 (Some(start_page), Some(end_page)) => {
388 number.insert(
389 NumberVariable::Page,
390 NumberLike::Str(format!("{}-{}", start_page, end_page).into()),
391 );
392 }
393 (Some(page), None) | (None, Some(page)) => {
394 number.insert(NumberVariable::Page, parse_number(page));
395 }
396 (None, None) => {}
397 }
398
399 if let Some(publisher) = self.publisher {
400 ordinary.insert(Variable::Publisher, publisher);
401 }
402
403 if let Some(reviewed_item) = self.reviewed_item {
404 ordinary.insert(Variable::ReviewedTitle, reviewed_item);
405 }
406
407 if let Some(section) = self.section {
408 ordinary.insert(Variable::Section, section);
409 }
410
411 if let Some(short_title) = self.short_title {
412 ordinary.insert(Variable::TitleShort, short_title);
413 }
414
415 if let Some(title) = self.title {
416 ordinary.insert(Variable::Title, title);
417 }
418
419 if let Some(volume) = self.volume {
420 number.insert(NumberVariable::Volume, parse_number(volume));
421 }
422
423 Reference {
424 id: Atom::from(self.id.or(self.label).expect("RIS id is missing").as_str()),
425 csl_type,
426 language: None,
427 name,
428 number,
429 date,
430 ordinary,
431 }
432 }
433 }
434
parse_number(value: String) -> NumberLike435 fn parse_number(value: String) -> NumberLike {
436 match value.parse() {
437 Ok(value) => NumberLike::Num(value),
438 Err(_) => NumberLike::Str(value.into()),
439 }
440 }
441
parse_authors(authors: Vec<String>) -> Vec<Name>442 fn parse_authors(authors: Vec<String>) -> Vec<Name> {
443 authors
444 .into_iter()
445 .map(|author| Name::Literal {
446 is_latin_cyrillic: is_latin_cyrillic(&author),
447 literal: author.into(),
448 })
449 .collect()
450 }
451
parse_date_or_range(value: String) -> DateOrRange452 fn parse_date_or_range(value: String) -> DateOrRange {
453 parse_date(&value)
454 .map(DateOrRange::Single)
455 .unwrap_or_else(|| DateOrRange::Literal {
456 literal: value.into(),
457 circa: false,
458 })
459 }
460
parse_date(value: &str) -> Option<Date>461 fn parse_date(value: &str) -> Option<Date> {
462 let mut parts = value.split('/');
463 let year: i32 = parts.next()?.parse().ok()?;
464 match parts.next().and_then(|p| p.parse().ok()) {
465 Some(month) => match parts.next().and_then(|p| p.parse().ok()) {
466 Some(day) => Some(Date::new(year, month, day)),
467 None => Some(Date::new(year, month, 0)),
468 },
469 None => Some(Date::new(year, 0, 0)),
470 }
471 }
472