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