nom_bibtex/
model.rs

1use crate::error::BibtexError;
2use crate::parser;
3use crate::parser::{Entry, Span, mkspan};
4use nom_language::error::VerboseError;
5use std::collections::HashMap;
6use std::result;
7use std::str;
8
9type Result<T> = result::Result<T, BibtexError>;
10
11const TABLE_MONTHS: [(&str, &str); 12] = [
12    ("jan", "January"),
13    ("feb", "February"),
14    ("mar", "March"),
15    ("apr", "April"),
16    ("may", "May"),
17    ("jun", "June"),
18    ("jul", "July"),
19    ("aug", "August"),
20    ("sep", "September"),
21    ("oct", "October"),
22    ("nov", "November"),
23    ("dec", "December"),
24];
25
26/// A high-level definition of a bibtex file.
27#[derive(Debug, PartialEq, Eq, Default)]
28pub struct Bibtex {
29    comments: Vec<String>,
30    preambles: Vec<String>,
31    const_map: HashMap<&'static str, &'static str>,
32    variables: HashMap<String, String>,
33    bibliographies: Vec<Bibliography>,
34}
35
36impl Bibtex {
37    /// Create a new Bibtex instance from a *BibTeX* file content.
38    pub fn parse(bibtex: &str) -> Result<Self> {
39        let entries = Self::raw_parse(bibtex)?;
40
41        let mut bibtex = Bibtex::default();
42
43        Self::fill_constants(&mut bibtex)?;
44        Self::fill_variables(&mut bibtex, &entries)?;
45
46        for entry in entries {
47            match entry {
48                Entry::Variable(_) => continue, // Already handled.
49                Entry::Comment(v) => bibtex.comments.push(v),
50                Entry::Preamble(v) => {
51                    let new_val = Self::expand_str_abbreviations(v, &bibtex)?;
52                    bibtex.preambles.push(new_val);
53                }
54                Entry::Bibliography(entry_t, citation_key, tags) => {
55                    let new_tags = tags
56                        .into_iter()
57                        .filter_map(|tag| {
58                            let key = tag.key.to_lowercase();
59                            let value = Self::expand_str_abbreviations(tag.value, &bibtex).ok()?;
60                            Some((key, value))
61                        })
62                        .collect::<HashMap<_, _>>();
63
64                    bibtex
65                        .bibliographies
66                        .push(Bibliography::new(entry_t, citation_key, new_tags));
67                }
68            }
69        }
70        Ok(bibtex)
71    }
72
73    /// Get a raw vector of entries in order from the files.
74    pub fn raw_parse(bibtex: &str) -> Result<Vec<Entry>> {
75        let span = mkspan(bibtex);
76        match parser::entries::<VerboseError<Span>>(span) {
77            Ok((_, v)) => Ok(v),
78            Err(e) => Err(BibtexError::with_context(bibtex, e)),
79        }
80    }
81
82    /// Get preambles with expanded variables.
83    pub fn preambles(&self) -> &[String] {
84        &self.preambles
85    }
86
87    /// Get comments.
88    pub fn comments(&self) -> &[String] {
89        &self.comments
90    }
91
92    /// Get string variables with a tuple of key and expanded value.
93    ///
94    /// The keys in the HashMap use lowercase.
95    pub fn variables(&self) -> &HashMap<String, String> {
96        &self.variables
97    }
98
99    /// Get bibliographies entry with variables expanded.
100    pub fn bibliographies(&self) -> &Vec<Bibliography> {
101        &self.bibliographies
102    }
103
104    fn fill_constants(bibtex: &mut Bibtex) -> Result<()> {
105        for m in &TABLE_MONTHS {
106            bibtex.const_map.insert(m.0, m.1);
107        }
108        Ok(())
109    }
110
111    fn fill_variables(bibtex: &mut Bibtex, entries: &[Entry]) -> Result<()> {
112        let variables = entries
113            .iter()
114            .filter_map(|v| match v {
115                Entry::Variable(v) => Some(v),
116                _ => None,
117            })
118            .collect::<Vec<_>>();
119
120        bibtex.variables = variables
121            .iter()
122            .filter_map(|var| {
123                let value = Self::expand_variables_value(&var.value, &variables).ok()?;
124                Some((var.key.clone(), value))
125            })
126            .collect();
127
128        Ok(())
129    }
130
131    fn expand_variables_value(
132        var_values: &Vec<StringValueType>,
133        variables: &Vec<&KeyValue>,
134    ) -> Result<String> {
135        let mut result_value = String::new();
136
137        for chunck in var_values {
138            match chunck.clone() {
139                StringValueType::Str(v) => result_value.push_str(&v),
140                StringValueType::Abbreviation(v) => {
141                    let var = variables
142                        .iter()
143                        .find(|&x| *v == x.key)
144                        .ok_or(BibtexError::StringVariableNotFound(v))?;
145                    result_value.push_str(&Self::expand_variables_value(&var.value, variables)?);
146                }
147            }
148        }
149        Ok(result_value)
150    }
151
152    fn expand_str_abbreviations(value: Vec<StringValueType>, bibtex: &Bibtex) -> Result<String> {
153        let mut result = String::new();
154
155        for chunck in value {
156            match chunck {
157                StringValueType::Str(v) => result.push_str(&v),
158                StringValueType::Abbreviation(v) => {
159                    let var = bibtex.variables.iter().find(|&x| &v == x.0);
160                    if let Some(res) = var {
161                        result.push_str(res.1)
162                    } else {
163                        match bibtex.const_map.get(v.as_str()) {
164                            Some(res) => result.push_str(res),
165                            None => return Err(BibtexError::StringVariableNotFound(v)),
166                        }
167                    }
168                }
169            }
170        }
171        Ok(result)
172    }
173}
174
175/// This is the main representation of a bibliography.
176#[derive(Debug, PartialEq, Eq)]
177pub struct Bibliography {
178    entry_type: String,
179    citation_key: String,
180    tags: HashMap<String, String>,
181}
182
183impl Bibliography {
184    /// Create a new bibliography.
185    pub fn new(
186        entry_type: String,
187        citation_key: String,
188        tags: HashMap<String, String>,
189    ) -> Bibliography {
190        Bibliography {
191            entry_type,
192            citation_key,
193            tags,
194        }
195    }
196
197    /// Get the entry type.
198    ///
199    /// It represents the type of the publications such as article, book, ...
200    pub fn entry_type(&self) -> &str {
201        &self.entry_type
202    }
203
204    /// Get the citation key.
205    ///
206    /// The citation key is the the keyword used to reference the bibliography
207    /// in a LaTeX file for example.
208    pub fn citation_key(&self) -> &str {
209        &self.citation_key
210    }
211
212    /// Get the tags.
213    ///
214    /// Tags are the specifics information about a bibliography
215    /// such as author, date, title, ...
216    ///
217    /// The keys in the HashMap use lowercase.
218    pub fn tags(&self) -> &HashMap<String, String> {
219        &self.tags
220    }
221}
222
223/// Represent a Bibtex value which is composed of
224///
225/// - strings value
226/// - string variable/abbreviation which will be expanded after parsing.
227#[derive(Debug, PartialEq, Eq, Clone)]
228pub enum StringValueType {
229    /// Just a basic string.
230    Str(String),
231    /// An abbreviation that should match some string variable.
232    Abbreviation(String),
233}
234
235/// Representation of a key-value.
236///
237/// Only used by parsing.
238#[derive(Debug, PartialEq, Eq, Clone)]
239pub struct KeyValue {
240    pub key: String,
241    pub value: Vec<StringValueType>,
242}
243
244impl KeyValue {
245    pub fn new(key: String, value: Vec<StringValueType>) -> KeyValue {
246        Self { key, value }
247    }
248}