1#![warn(missing_docs)]
139#![allow(clippy::comparison_chain)]
140
141#[macro_use]
142mod selectors;
143#[cfg(feature = "biblatex")]
144mod interop;
145
146mod csl;
147pub mod io;
148pub mod lang;
149pub mod types;
150mod util;
151
152use std::collections::BTreeMap;
153
154#[cfg(feature = "archive")]
155pub use crate::csl::archive;
156pub use citationberg;
157pub use csl::{
158 BibliographyDriver, BibliographyItem, BibliographyRequest, Brackets, BufWriteFormat,
159 CitationItem, CitationRequest, CitePurpose, Elem, ElemChild, ElemChildren, ElemMeta,
160 Formatted, Formatting, LocatorPayload, Rendered, RenderedBibliography,
161 RenderedCitation, SpecificLocator, TransparentLocator, standalone_citation,
162};
163pub use selectors::{Selector, SelectorError};
164
165use indexmap::IndexMap;
166use paste::paste;
167use serde::{Deserialize, Serialize, de::Visitor};
168use types::*;
169use unic_langid::LanguageIdentifier;
170use util::{
171 OneOrMany, deserialize_one_or_many_opt, serialize_one_or_many,
172 serialize_one_or_many_opt,
173};
174
175#[derive(Debug, Clone, Default, PartialEq, Serialize)]
177pub struct Library(IndexMap<String, Entry>);
178
179impl Library {
180 pub fn new() -> Self {
182 Self(IndexMap::new())
183 }
184
185 pub fn push(&mut self, entry: &Entry) {
187 self.0.insert(entry.key.clone(), entry.clone());
188 }
189
190 pub fn get(&self, key: &str) -> Option<&Entry> {
192 self.0.get(key)
193 }
194
195 pub fn iter(&self) -> impl Iterator<Item = &Entry> {
197 self.0.values()
198 }
199
200 pub fn keys(&self) -> impl Iterator<Item = &str> {
202 self.0.keys().map(|k| k.as_str())
203 }
204
205 pub fn remove(&mut self, key: &str) -> Option<Entry> {
207 self.0.shift_remove(key)
208 }
209
210 pub fn len(&self) -> usize {
212 self.0.len()
213 }
214
215 pub fn is_empty(&self) -> bool {
217 self.0.is_empty()
218 }
219
220 pub fn nth(&self, n: usize) -> Option<&Entry> {
222 self.0.get_index(n).map(|(_, v)| v)
223 }
224}
225
226impl<'a> IntoIterator for &'a Library {
227 type Item = &'a Entry;
228 type IntoIter = indexmap::map::Values<'a, String, Entry>;
229
230 fn into_iter(self) -> Self::IntoIter {
231 self.0.values()
232 }
233}
234
235impl IntoIterator for Library {
236 type Item = Entry;
237 type IntoIter = std::iter::Map<
238 indexmap::map::IntoIter<String, Entry>,
239 fn((String, Entry)) -> Entry,
240 >;
241
242 fn into_iter(self) -> Self::IntoIter {
243 self.0.into_iter().map(|(_, v)| v)
244 }
245}
246
247impl FromIterator<Entry> for Library {
248 fn from_iter<T: IntoIterator<Item = Entry>>(iter: T) -> Self {
249 Self(iter.into_iter().map(|e| (e.key().to_string(), e)).collect())
250 }
251}
252
253macro_rules! entry {
254 ($(
255 $(#[doc = $doc:literal])*
256 $(#[serde $serde:tt])*
257 $s:literal => $i:ident : $t:ty
258 $(| $d:ty)? $(,)?
259 ),*) => {
260 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Hash)]
264 pub struct Entry {
265 #[serde(skip)]
267 key: String,
268 #[serde(rename = "type")]
270 entry_type: EntryType,
271 $(
272 $(#[doc = $doc])*
273 $(#[serde $serde])*
274 #[serde(skip_serializing_if = "Option::is_none")]
275 #[serde(rename = $s)]
276 $i: Option<$t>,
277 )*
278 #[serde(serialize_with = "serialize_one_or_many")]
281 #[serde(skip_serializing_if = "Vec::is_empty")]
282 #[serde(rename = "parent")]
283 parents: Vec<Entry>,
284 }
285
286 impl Entry {
287 pub fn key(&self) -> &str {
289 &self.key
290 }
291
292 pub fn new(key: &str, entry_type: EntryType) -> Self {
294 Self {
295 key: key.to_owned(),
296 entry_type,
297 $(
298 $i: None,
299 )*
300 parents: Vec::new(),
301 }
302 }
303
304 pub fn has(&self, key: &str) -> bool {
306 match key {
307 $(
308 $s => self.$i.is_some(),
309 )*
310 _ => false,
311 }
312 }
313 }
314
315 impl Entry {
317 pub fn entry_type(&self) -> &EntryType {
319 &self.entry_type
320 }
321
322 pub fn parents(&self) -> &[Entry] {
324 &self.parents
325 }
326
327 $(
328 entry!(@get $(#[doc = $doc])* $s => $i : $t $(| $d)?);
329 )*
330 }
331
332 impl Entry {
334 pub fn set_parents(&mut self, parents: Vec<Entry>) {
336 self.parents = parents;
337 }
338
339
340 $(
341 entry!(@set $s => $i : $t);
342 )*
343 }
344
345 impl<'de> Deserialize<'de> for Library {
351 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
352 where
353 D: serde::Deserializer<'de>,
354 {
355 struct MyVisitor;
356
357 #[derive(Deserialize)]
358 struct NakedEntry {
359 #[serde(rename = "type")]
360 entry_type: Option<EntryType>,
361 #[serde(default)]
362 #[serde(rename = "parent")]
363 parents: OneOrMany<NakedEntry>,
364 $(
365 $(#[serde $serde])*
366 #[serde(rename = $s)]
367 #[serde(default)]
368 $i: Option<$t>,
369 )*
370 }
371
372 impl NakedEntry {
373 fn into_entry<E>(
376 self,
377 key: &str,
378 child_entry_type: Option<EntryType>,
379 ) -> Result<Entry, E>
380 where E: serde::de::Error
381 {
382 let entry_type = self.entry_type
383 .or_else(|| child_entry_type.map(|e| e.default_parent()))
384 .ok_or_else(|| E::custom("no entry type"))?;
385
386 let parents: Result<Vec<_>, _> = self.parents
387 .into_iter()
388 .map(|p| p.into_entry(key, Some(entry_type)))
389 .collect();
390
391 Ok(Entry {
392 key: key.to_owned(),
393 entry_type,
394 parents: parents?,
395 $(
396 $i: self.$i,
397 )*
398 })
399 }
400 }
401
402 impl<'de> Visitor<'de> for MyVisitor {
403 type Value = Library;
404
405 fn expecting(&self, formatter: &mut std::fmt::Formatter)
406 -> std::fmt::Result
407 {
408 formatter.write_str(
409 "a map between cite keys and entries"
410 )
411 }
412
413 fn visit_map<A>(self, mut map: A)
414 -> Result<Self::Value, A::Error>
415 where
416 A: serde::de::MapAccess<'de>,
417 {
418 let mut entries = Vec::with_capacity(
419 map.size_hint().unwrap_or(0).min(128)
420 );
421 while let Some(key) = map.next_key::<String>()? {
422 if entries.iter().any(|(k, _)| k == &key) {
423 return Err(serde::de::Error::custom(format!(
424 "duplicate key {}",
425 key
426 )));
427 }
428
429 let entry: NakedEntry = map.next_value()?;
430 entries.push((key, entry));
431 }
432
433 let entries: Result<IndexMap<_, _>, A::Error> =
434 entries.into_iter().map(|(k, v)| {
435 v.into_entry(&k, None).map(|e| (k, e))
436 }).collect();
437
438 Ok(Library(entries?))
439 }
440 }
441
442 deserializer.deserialize_map(MyVisitor)
443 }
444 }
445 };
446
447 (@match
448 $s:literal => $i:ident,
449 $naked:ident, $map:ident $(,)?
450 ) => {
451 $naked.$i = Some($map.next_value()?)
452 };
453
454 (@match
456 $(#[serde $serde:tt])+
457 $s:literal => $i:ident,
458 $naked:ident, $map:ident $(,)?
459 ) => {
460 let one_or_many: OneOrMany = $map.next_value()?;
461 $naked.$i = Some(one_or_many.into());
462 };
463
464 (@get $(#[$docs:meta])+ $s:literal => $i:ident : $t:ty | $d:ty $(,)?) => {
466 $(#[$docs])+
467 pub fn $i(&self) -> Option<&$d> {
468 self.$i.as_deref()
469 }
470 };
471
472 (@get $(#[$docs:meta])+ $s:literal => $i:ident : $t:ty $(,)?) => {
474 $(#[$docs])+
475 pub fn $i(&self) -> Option<&$t> {
476 self.$i.as_ref()
477 }
478 };
479
480 (@set $s:literal => $i:ident : $t:ty $(,)?) => {
482 paste! {
483 #[doc = "Set the `" $s "` field."]
484 pub fn [<set_ $i>](&mut self, $i: $t) {
485 self.$i = Some($i);
486 }
487 }
488 };
489}
490
491entry! {
492 "title" => title: FormatString,
494 #[serde(serialize_with = "serialize_one_or_many_opt")]
496 #[serde(deserialize_with = "deserialize_one_or_many_opt")]
497 "author" => authors: Vec<Person> | [Person],
498 "date" => date: Date,
500 #[serde(serialize_with = "serialize_one_or_many_opt")]
502 #[serde(deserialize_with = "deserialize_one_or_many_opt")]
503 "editor" => editors: Vec<Person> | [Person],
504 #[serde(serialize_with = "serialize_one_or_many_opt")]
506 #[serde(deserialize_with = "deserialize_one_or_many_opt")]
507 "affiliated" => affiliated: Vec<PersonsWithRoles> | [PersonsWithRoles],
508 "publisher" => publisher: Publisher,
510 "location" => location: FormatString,
512 "organization" => organization: FormatString,
514 "issue" => issue: MaybeTyped<Numeric>,
517 "chapter" => chapter: MaybeTyped<Numeric>,
523 "volume" => volume: MaybeTyped<Numeric>,
526 "volume-total" => volume_total: Numeric,
528 "edition" => edition: MaybeTyped<Numeric>,
530 "page-range" => page_range: MaybeTyped<PageRanges>,
532 "page-total" => page_total: Numeric,
534 "time-range" => time_range: MaybeTyped<DurationRange>,
536 "runtime" => runtime: MaybeTyped<Duration>,
538 "url" => url: QualifiedUrl,
540 #[serde(alias = "serial")]
544 "serial-number" => serial_number: SerialNumber,
545 "language" => language: LanguageIdentifier,
547 "archive" => archive: FormatString,
549 "archive-location" => archive_location: FormatString,
551 "call-number" => call_number: FormatString,
553 "note" => note: FormatString,
555 "abstract" => abstract_: FormatString,
557 "genre" => genre: FormatString,
561}
562
563impl Entry {
564 pub(crate) fn affiliated_with_role(&self, role: PersonRole) -> Vec<&Person> {
567 self.affiliated
568 .iter()
569 .flatten()
570 .filter_map(
571 |PersonsWithRoles { names, role: r }| {
572 if r == &role { Some(names) } else { None }
573 },
574 )
575 .flatten()
576 .collect()
577 }
578
579 pub fn map<'a, F, T>(&'a self, mut f: F) -> Option<T>
582 where
583 F: FnMut(&'a Self) -> Option<T>,
584 {
585 if let Some(value) = f(self) { Some(value) } else { self.map_parents(f) }
586 }
587
588 pub fn map_parents<'a, F, T>(&'a self, mut f: F) -> Option<T>
590 where
591 F: FnMut(&'a Self) -> Option<T>,
592 {
593 let mut path: Vec<usize> = vec![0];
594 let up = |path: &mut Vec<usize>| {
595 path.pop();
596 if let Some(last) = path.last_mut() {
597 *last += 1;
598 }
599 };
600
601 'outer: loop {
602 let first_path = path.first()?;
606
607 if self.parents.len() <= *first_path {
608 return None;
609 }
610
611 let mut item = &self.parents[*first_path];
612
613 for i in 1..path.len() {
614 if path[i] >= item.parents.len() {
615 up(&mut path);
616 continue 'outer;
617 }
618 item = &item.parents[path[i]];
619 }
620
621 if let Some(first_path) = path.first_mut() {
622 *first_path += 1;
623 }
624
625 if let Some(value) = f(item) {
626 return Some(value);
627 }
628 }
629 }
630
631 pub fn bound_select(&self, selector: &Selector, binding: &str) -> Option<&Entry> {
633 selector.apply(self).and_then(|map| map.get(binding).copied())
634 }
635
636 pub fn date_any(&self) -> Option<&Date> {
638 self.map(|e| e.date.as_ref())
639 }
640
641 pub fn url_any(&self) -> Option<&QualifiedUrl> {
643 self.map(|e| e.url.as_ref())
644 }
645
646 pub fn keyed_serial_number(&self, key: &str) -> Option<&str> {
648 self.serial_number
649 .as_ref()
650 .and_then(|s| s.0.get(key).map(|s| s.as_str()))
651 }
652
653 pub fn set_keyed_serial_number(&mut self, key: &str, value: String) {
655 if let Some(serials) = &mut self.serial_number {
656 serials.0.insert(key.to_owned(), value);
657 } else {
658 let mut map = BTreeMap::new();
659 map.insert(key.to_owned(), value);
660 self.serial_number = Some(SerialNumber(map));
661 }
662 }
663
664 pub fn doi(&self) -> Option<&str> {
666 self.keyed_serial_number("doi")
667 }
668
669 pub fn set_doi(&mut self, doi: String) {
671 self.set_keyed_serial_number("doi", doi);
672 }
673
674 pub fn isbn(&self) -> Option<&str> {
676 self.keyed_serial_number("isbn")
677 }
678
679 pub fn set_isbn(&mut self, isbn: String) {
681 self.set_keyed_serial_number("isbn", isbn);
682 }
683
684 pub fn issn(&self) -> Option<&str> {
686 self.keyed_serial_number("issn")
687 }
688
689 pub fn set_issn(&mut self, issn: String) {
691 self.set_keyed_serial_number("issn", issn);
692 }
693
694 pub fn pmid(&self) -> Option<&str> {
696 self.keyed_serial_number("pmid")
697 }
698
699 pub fn set_pmid(&mut self, pmid: String) {
701 self.set_keyed_serial_number("pmid", pmid);
702 }
703
704 pub fn pmcid(&self) -> Option<&str> {
706 self.keyed_serial_number("pmcid")
707 }
708
709 pub fn set_pmcid(&mut self, pmcid: String) {
711 self.set_keyed_serial_number("pmcid", pmcid);
712 }
713
714 pub fn arxiv(&self) -> Option<&str> {
716 self.keyed_serial_number("arxiv")
717 }
718
719 pub fn set_arxiv(&mut self, arxiv: String) {
721 self.set_keyed_serial_number("arxiv", arxiv);
722 }
723
724 pub(crate) fn get_container(&self) -> Option<&Self> {
726 let retrieve_container = |possible: &[EntryType]| {
727 for possibility in possible {
728 if let Some(container) =
729 self.parents.iter().find(|e| e.entry_type == *possibility)
730 {
731 return Some(container);
732 }
733 }
734
735 None
736 };
737
738 match &self.entry_type {
739 EntryType::Article => retrieve_container(&[
740 EntryType::Book,
741 EntryType::Proceedings,
745 EntryType::Conference,
746 EntryType::Periodical,
747 EntryType::Newspaper,
748 EntryType::Blog,
749 EntryType::Reference,
750 EntryType::Web,
751 ]),
752 EntryType::Anthos => retrieve_container(&[
753 EntryType::Book,
754 EntryType::Anthology,
755 EntryType::Reference,
756 EntryType::Report,
757 ]),
758 EntryType::Chapter => retrieve_container(&[
759 EntryType::Book,
760 EntryType::Anthology,
761 EntryType::Reference,
762 EntryType::Report,
763 ]),
764 EntryType::Report => {
765 retrieve_container(&[EntryType::Book, EntryType::Anthology])
766 }
767 EntryType::Web => retrieve_container(&[EntryType::Web]),
768 EntryType::Scene => retrieve_container(&[
769 EntryType::Audio,
770 EntryType::Video,
771 EntryType::Performance,
772 EntryType::Artwork,
773 ]),
774 EntryType::Case => retrieve_container(&[
775 EntryType::Book,
776 EntryType::Anthology,
777 EntryType::Reference,
778 EntryType::Report,
779 ]),
780 EntryType::Post => {
781 retrieve_container(&[EntryType::Thread, EntryType::Blog, EntryType::Web])
782 }
783 EntryType::Thread => {
784 retrieve_container(&[EntryType::Thread, EntryType::Web, EntryType::Blog])
785 }
786 _ => None,
787 }
788 }
789
790 pub(crate) fn get_full(&self) -> &Self {
792 let mut parent = self.parents().first();
793 let mut entry = self;
794 while select!(Chapter | Scene).matches(entry) && entry.title().is_none() {
795 if let Some(p) = parent {
796 entry = p;
797 parent = entry.parents().first();
798 } else {
799 break;
800 }
801 }
802
803 entry
804 }
805
806 pub(crate) fn get_collection(&self) -> Option<&Self> {
808 match &self.entry_type {
809 EntryType::Anthology
810 | EntryType::Newspaper
811 | EntryType::Performance
812 | EntryType::Periodical
813 | EntryType::Proceedings
814 | EntryType::Book
815 | EntryType::Reference
816 | EntryType::Exhibition => self.parents.iter().find(|e| {
817 e.entry_type == self.entry_type || e.entry_type == EntryType::Anthology
818 }),
819 _ => self.parents.iter().find_map(|e| e.get_collection()),
820 }
821 }
822
823 pub(crate) fn dfs_parent(&self, kind: EntryType) -> Option<&Self> {
825 if self.entry_type == kind {
826 return Some(self);
827 }
828
829 for parent in &self.parents {
830 if let Some(entry) = parent.dfs_parent(kind) {
831 return Some(entry);
832 }
833 }
834
835 None
836 }
837
838 pub(crate) fn get_original(&self) -> Option<&Self> {
840 self.dfs_parent(EntryType::Original)
841 }
842}
843
844#[cfg(feature = "biblatex")]
845impl Entry {
846 pub(crate) fn add_parent(&mut self, entry: Self) {
849 self.parents.push(entry);
850 }
851
852 pub(crate) fn add_affiliated_persons(
854 &mut self,
855 new_persons: (Vec<Person>, PersonRole),
856 ) {
857 let obj = PersonsWithRoles { names: new_persons.0, role: new_persons.1 };
858 if let Some(affiliated) = &mut self.affiliated {
859 affiliated.push(obj);
860 } else {
861 self.affiliated = Some(vec![obj]);
862 }
863 }
864
865 pub(crate) fn parents_mut(&mut self) -> &mut [Self] {
866 &mut self.parents
867 }
868}
869
870#[cfg(test)]
871mod tests {
872 use std::fs;
873
874 use super::*;
875 use crate::io::from_yaml_str;
876
877 macro_rules! select_all {
878 ($select:expr, $entries:tt, [$($key:expr),* $(,)*] $(,)*) => {
879 let keys = [$($key,)*];
880 let selector = Selector::parse($select).unwrap();
881 for entry in $entries.iter() {
882 let res = selector.apply(entry);
883 if keys.contains(&entry.key.as_str()) {
884 if res.is_none() {
885 panic!("Key {} not found in results", entry.key);
886 }
887 } else {
888 if res.is_some() {
889 panic!("Key {} found in results", entry.key);
890 }
891 }
892 }
893 }
894 }
895
896 macro_rules! select {
897 ($select:expr, $entries:tt >> $entry_key:expr, [$($key:expr),* $(,)*] $(,)*) => {
898 let keys = vec![ $( $key , )* ];
899 let entry = $entries.iter().filter_map(|i| if i.key == $entry_key {Some(i)} else {None}).next().unwrap();
900 let selector = Selector::parse($select).unwrap();
901 let res = selector.apply(entry).unwrap();
902 if !keys.into_iter().all(|k| res.get(k).is_some()) {
903 panic!("Results do not contain binding");
904 }
905 }
906 }
907
908 #[test]
909 fn selectors() {
910 let contents = fs::read_to_string("tests/data/basic.yml").unwrap();
911 let entries = from_yaml_str(&contents).unwrap();
912
913 select_all!("article > proceedings", entries, ["zygos"]);
914 select_all!(
915 "article > (periodical | newspaper)",
916 entries,
917 ["omarova-libra", "kinetics", "house", "swedish",]
918 );
919 select_all!(
920 "(chapter | anthos) > (anthology | book)",
921 entries,
922 ["harry", "gedanken", "lamb-chapter", "snail-chapter"]
923 );
924 select_all!(
925 "*[url]",
926 entries,
927 [
928 "omarova-libra",
929 "science-e-issue",
930 "oiseau",
931 "georgia",
932 "really-habitable",
933 "electronic-music",
934 "mattermost",
935 "worth",
936 "wrong",
937 "un-hdr",
938 "audio-descriptions",
939 "camb",
940 "logician",
941 "dns-encryption",
942 "overleaf",
943 "editors",
944 ]
945 );
946 select_all!(
947 "!(*[url] | (* > *[url]))",
948 entries,
949 [
950 "zygos",
951 "harry",
952 "terminator-2",
953 "interior",
954 "wire",
955 "kinetics",
956 "house",
957 "plaque",
958 "renaissance",
959 "gedanken",
960 "donne",
961 "roe-wade",
962 "foia",
963 "drill",
964 "swedish",
965 "latex-users",
966 "barb",
967 "lamb",
968 "snail",
969 "lamb-chapter",
970 "snail-chapter",
971 ]
972 );
973 select_all!("*[abstract, note, genre]", entries, ["wire"]);
974 }
975
976 #[test]
977 fn selector_bindings() {
978 let contents = fs::read_to_string("tests/data/basic.yml").unwrap();
979 let entries = from_yaml_str(&contents).unwrap();
980
981 select!(
982 "a:article > (b:conference & c:(video|blog|web))",
983 entries >> "wwdc-network",
984 ["a", "b", "c"]
985 );
986 }
987
988 #[test]
989 #[cfg(feature = "biblatex")]
990 fn test_troublesome_page_ranges() {
991 use io::from_biblatex_str;
992
993 let bibtex = r#"
994 @article{b,
995 title={My page ranges},
996 pages={150--es}
997 }
998 "#;
999
1000 let library = from_biblatex_str(bibtex).unwrap();
1001
1002 for entry in library.iter() {
1003 assert!(entry.page_range.is_some())
1004 }
1005 }
1006}