diff options
Diffstat (limited to 'support/texlab/src/citation/driver.rs')
-rw-r--r-- | support/texlab/src/citation/driver.rs | 733 |
1 files changed, 733 insertions, 0 deletions
diff --git a/support/texlab/src/citation/driver.rs b/support/texlab/src/citation/driver.rs new file mode 100644 index 0000000000..eaf98304c4 --- /dev/null +++ b/support/texlab/src/citation/driver.rs @@ -0,0 +1,733 @@ +use isocountry::CountryCode; +use itertools::Itertools; +use titlecase::titlecase; +use url::Url; + +use crate::syntax::bibtex; + +use super::{ + entry::{EntryData, EntryKind}, + field::{ + author::AuthorField, + date::DateField, + number::{NumberField, NumberFieldData}, + text::TextField, + }, + output::{Inline, InlineBuilder, Punct}, +}; + +#[derive(Debug, Default)] +pub struct Driver { + builder: InlineBuilder, +} + +impl Driver { + pub fn process(&mut self, entry: &bibtex::Entry) -> Option<()> { + let entry = EntryData::parse(entry)?; + match entry.kind { + EntryKind::Article + | EntryKind::DataSet + | EntryKind::Manual + | EntryKind::Misc + | EntryKind::Online + | EntryKind::Electronic + | EntryKind::Www + | EntryKind::Proceedings + | EntryKind::MVProceedings + | EntryKind::Set + | EntryKind::Software + | EntryKind::Thesis + | EntryKind::MasterThesis + | EntryKind::PhdThesis + | EntryKind::Unknown => self.article_like(entry), + EntryKind::Book + | EntryKind::MVBook + | EntryKind::InBook + | EntryKind::BookInBook + | EntryKind::SuppBook + | EntryKind::Booklet + | EntryKind::Collection + | EntryKind::MVCollection + | EntryKind::InCollection + | EntryKind::SuppCollection + | EntryKind::Periodical + | EntryKind::SuppPeriodical + | EntryKind::InProceedings + | EntryKind::Conference + | EntryKind::Reference + | EntryKind::MVReference + | EntryKind::InReference + | EntryKind::Report + | EntryKind::TechReport => self.book_like(entry), + EntryKind::Patent => self.patent(entry), + } + } + + fn article_like(&mut self, mut entry: EntryData) -> Option<()> { + self.author(&mut entry); + self.title(&mut entry); + self.translator(&mut entry); + self.commentator(&mut entry); + self.annotator(&mut entry); + self.version(&mut entry); + self.journal(&mut entry); + self.article_series(&mut entry); + + if let Some(volume) = entry.number.remove(&NumberField::Volume) { + let number = entry + .number + .remove(&NumberField::Number) + .map(|data| format!(".{}", data)) + .unwrap_or_default(); + + self.builder.push( + Inline::Regular(format!("{}{}", volume, number)), + Punct::Space, + Punct::Space, + ); + } + + self.eid(&mut entry); + self.issue_and_date(&mut entry); + self.issue_title(&mut entry); + self.editors(&mut entry); + self.note(&mut entry); + self.pages(&mut entry); + self.issn(&mut entry); + self.doi(&mut entry); + self.eprint(&mut entry); + self.url(&mut entry); + self.addendum(&mut entry); + self.pubstate(&mut entry); + Some(()) + } + + fn book_like(&mut self, mut entry: EntryData) -> Option<()> { + self.author(&mut entry); + self.title(&mut entry); + self.inbook_title(&mut entry); + self.main_title(&mut entry); + self.event(&mut entry); + self.editors(&mut entry); + self.translator(&mut entry); + self.commentator(&mut entry); + self.annotator(&mut entry); + self.introduction(&mut entry); + self.foreword(&mut entry); + self.afterword(&mut entry); + self.edition(&mut entry); + self.book_volume(&mut entry); + self.volumes(&mut entry); + self.book_series(&mut entry); + self.how_published(&mut entry); + self.note(&mut entry); + self.publisher(&mut entry); + self.date(&mut entry); + self.chapter(&mut entry); + self.eid(&mut entry); + self.pages(&mut entry); + self.page_total(&mut entry); + self.isbn(&mut entry); + self.doi(&mut entry); + self.eprint(&mut entry); + self.url(&mut entry); + self.addendum(&mut entry); + self.pubstate(&mut entry); + Some(()) + } + + fn patent(&mut self, mut entry: EntryData) -> Option<()> { + self.author(&mut entry); + self.title(&mut entry); + self.patent_number(&mut entry); + self.holder(&mut entry); + self.date(&mut entry); + self.doi(&mut entry); + self.eprint(&mut entry); + self.url(&mut entry); + self.addendum(&mut entry); + self.pubstate(&mut entry); + Some(()) + } + + fn author(&mut self, entry: &mut EntryData) -> Option<()> { + let author = entry.author.remove(&AuthorField::Author)?; + self.builder.push( + Inline::Regular(author.to_string()), + Punct::Nothing, + Punct::Colon, + ); + + Some(()) + } + + fn commentator(&mut self, entry: &mut EntryData) -> Option<()> { + let commentator = entry.author.remove(&AuthorField::Commentator)?; + self.builder.push( + Inline::Regular(format!("With a comment. by {}", commentator)), + Punct::Dot, + Punct::Dot, + ); + + Some(()) + } + + fn translator(&mut self, entry: &mut EntryData) -> Option<()> { + let translator = entry.author.remove(&AuthorField::Translator)?; + self.builder.push( + Inline::Regular(format!("Trans. by {}", translator)), + Punct::Dot, + Punct::Dot, + ); + + Some(()) + } + + fn annotator(&mut self, entry: &mut EntryData) -> Option<()> { + let annotator = entry.author.remove(&AuthorField::Annotator)?; + self.builder.push( + Inline::Regular(format!("With annots. by {}", annotator)), + Punct::Dot, + Punct::Dot, + ); + + Some(()) + } + + fn editors(&mut self, entry: &mut EntryData) { + self.editor(entry, AuthorField::Editor, TextField::EditorType); + self.editor(entry, AuthorField::EditorA, TextField::EditorTypeA); + self.editor(entry, AuthorField::EditorB, TextField::EditorTypeB); + self.editor(entry, AuthorField::EditorC, TextField::EditorTypeC); + } + + fn editor( + &mut self, + entry: &mut EntryData, + name_field: AuthorField, + type_field: TextField, + ) -> Option<()> { + let editor = entry.author.remove(&name_field)?; + let editor_type = entry + .text + .remove(&type_field) + .map(|data| data.text) + .unwrap_or_else(|| "Ed. by".to_string()); + + self.builder.push( + Inline::Regular(format!("{editor_type} {editor}")), + Punct::Dot, + Punct::Dot, + ); + + Some(()) + } + + fn title(&mut self, entry: &mut EntryData) -> Option<()> { + let title = entry.text.remove(&TextField::Title)?; + let subtitle = entry + .text + .remove(&TextField::Subtitle) + .map(|data| format!(". {}", data.text)) + .unwrap_or_default(); + + self.builder.push( + Inline::Quoted(format!("{}{}", title.text, subtitle)), + Punct::Nothing, + Punct::Dot, + ); + + let addon = entry.text.remove(&TextField::TitleAddon)?; + self.builder + .push(Inline::Regular(addon.text), Punct::Dot, Punct::Dot); + + Some(()) + } + + fn main_title(&mut self, entry: &mut EntryData) -> Option<()> { + let title = entry.text.remove(&TextField::MainTitle)?; + let subtitle = entry + .text + .remove(&TextField::MainSubtitle) + .map(|data| format!(". {}", data.text)) + .unwrap_or_default(); + + self.builder.push( + Inline::Italic(titlecase(&format!("{}{}", title.text, subtitle))), + Punct::Dot, + Punct::Dot, + ); + + let addon = entry.text.remove(&TextField::MainTitleAddon)?; + self.builder + .push(Inline::Regular(addon.text), Punct::Dot, Punct::Dot); + + Some(()) + } + + fn inbook_title(&mut self, entry: &mut EntryData) -> Option<()> { + let title = entry.text.remove(&TextField::BookTitle)?; + let subtitle = entry + .text + .remove(&TextField::BookSubtitle) + .map(|data| format!(". {}", data.text)) + .unwrap_or_default(); + + self.builder.push( + Inline::Italic(titlecase(&format!("{}{}", title.text, subtitle))), + Punct::Dot, + Punct::Dot, + ); + + let addon = entry.text.remove(&TextField::BookTitleAddon)?; + self.builder + .push(Inline::Regular(addon.text), Punct::Dot, Punct::Dot); + + Some(()) + } + + fn issue_title(&mut self, entry: &mut EntryData) -> Option<()> { + let issue = entry.text.remove(&TextField::IssueTitle)?; + let subtitle = entry + .text + .remove(&TextField::IssueSubtitle) + .map(|data| format!(" {}", data.text)) + .unwrap_or_default(); + + self.builder.push( + Inline::Italic(titlecase(&format!("{}{}", issue.text, subtitle))), + Punct::Colon, + Punct::Space, + ); + + if let Some(addon) = entry.text.remove(&TextField::IssueTitleAddon) { + self.builder + .push(Inline::Regular(addon.text), Punct::Dot, Punct::Dot); + } + + Some(()) + } + + fn event(&mut self, entry: &mut EntryData) -> Option<()> { + let title = entry.text.remove(&TextField::EventTitle)?; + let addon = entry + .text + .remove(&TextField::EventTitleAddon) + .map_or(String::new(), |addon| format!(". {}", addon.text)); + + let venue = entry.text.remove(&TextField::Venue); + let date = entry.date.remove(&DateField::EventDate); + let venue_and_date = match (venue, date) { + (None, None) => String::new(), + (None, Some(date)) => format!(" ({date})"), + (Some(venue), None) => format!(" ({})", venue.text), + (Some(venue), Some(date)) => format!(" ({}, {})", venue.text, date), + }; + + self.builder.push( + Inline::Regular(format!("{}{}{}", title.text, addon, venue_and_date)), + Punct::Dot, + Punct::Dot, + ); + + Some(()) + } + + fn patent_number(&mut self, entry: &mut EntryData) -> Option<()> { + let number = entry.number.remove(&NumberField::Number)?; + + let location = entry + .text + .remove(&TextField::Location) + .map_or(String::new(), |location| { + let text = location + .text + .split_whitespace() + .map(|word| { + if word == "countryuk" { + "United Kingdom" + } else { + word.strip_prefix("country") + .and_then(|code| CountryCode::for_alpha2_caseless(code).ok()) + .map_or(word, |country| country.name()) + } + }) + .join(" "); + + format!(" ({})", text) + }); + + self.builder.push( + Inline::Regular(format!("{}{}", number, location)), + Punct::Dot, + Punct::Dot, + ); + + Some(()) + } + + fn holder(&mut self, entry: &mut EntryData) -> Option<()> { + let holder = entry.text.remove(&TextField::Holder)?; + self.builder + .push(Inline::Regular(holder.text), Punct::Dot, Punct::Dot); + + Some(()) + } + + fn version(&mut self, entry: &mut EntryData) -> Option<()> { + let version = entry.text.remove(&TextField::Version)?; + self.builder.push( + Inline::Regular(format!("Version {}", version.text)), + Punct::Dot, + Punct::Dot, + ); + + Some(()) + } + + fn article_series(&mut self, entry: &mut EntryData) -> Option<()> { + let series = entry.text.remove(&TextField::Series)?; + self.builder + .push(Inline::Regular(series.text), Punct::Space, Punct::Space); + + Some(()) + } + + fn issue_and_date(&mut self, entry: &mut EntryData) -> Option<()> { + let date = [DateField::Date, DateField::Year, DateField::Month] + .iter() + .filter_map(|field| entry.date.remove(field)) + .reduce(|a, b| a + b)?; + + let issue = entry + .text + .remove(&TextField::Issue) + .map(|data| format!("{} ", data.text)) + .unwrap_or_default(); + + self.builder.push( + Inline::Regular(format!("({issue}{date})")), + Punct::Space, + Punct::Colon, + ); + + Some(()) + } + + fn chapter(&mut self, entry: &mut EntryData) -> Option<()> { + let chapter = entry.text.remove(&TextField::Chapter)?; + self.builder.push( + Inline::Regular(format!("Chap. {}", chapter.text)), + Punct::Dot, + Punct::Dot, + ); + + Some(()) + } + + fn date(&mut self, entry: &mut EntryData) -> Option<()> { + let date = [DateField::Date, DateField::Year, DateField::Month] + .iter() + .filter_map(|field| entry.date.remove(field)) + .reduce(|a, b| a + b)?; + + self.builder + .push(Inline::Regular(format!("{date}")), Punct::Dot, Punct::Dot); + + Some(()) + } + + fn how_published(&mut self, entry: &mut EntryData) -> Option<()> { + let how_published = entry.text.remove(&TextField::HowPublished)?; + self.builder + .push(Inline::Regular(how_published.text), Punct::Dot, Punct::Dot); + + Some(()) + } + + fn publisher(&mut self, entry: &mut EntryData) -> Option<()> { + let publisher = entry.text.remove(&TextField::Publisher)?.text; + let location = entry + .text + .remove(&TextField::Location) + .map_or(String::new(), |location| format!("{}: ", location.text)); + + self.builder.push( + Inline::Regular(format!("{location}{publisher}")), + Punct::Dot, + Punct::Comma, + ); + + Some(()) + } + + fn book_series(&mut self, entry: &mut EntryData) -> Option<()> { + let series = entry.text.remove(&TextField::Series)?; + let number = entry + .number + .remove(&NumberField::Number) + .map_or(String::new(), |number| format!(" {}", number)); + + self.builder.push( + Inline::Regular(format!("{}{}", series.text, number)), + Punct::Dot, + Punct::Dot, + ); + + Some(()) + } + + fn volumes(&mut self, entry: &mut EntryData) -> Option<()> { + let volumes = entry.number.remove(&NumberField::Volume)?; + self.builder.push( + Inline::Regular(format!("{volumes} vols")), + Punct::Dot, + Punct::Dot, + ); + + Some(()) + } + + fn book_volume(&mut self, entry: &mut EntryData) -> Option<()> { + let volume = entry.number.remove(&NumberField::Volume)?; + let part = entry + .number + .remove(&NumberField::Part) + .map_or(String::new(), |part| format!(".{part}")); + + self.builder.push( + Inline::Regular(format!("Vol. {}{}", volume, part)), + Punct::Dot, + Punct::Dot, + ); + + Some(()) + } + + fn pubstate(&mut self, entry: &mut EntryData) -> Option<()> { + let pubstate = entry.text.remove(&TextField::Pubstate)?; + self.builder + .push(Inline::Regular(pubstate.text), Punct::Dot, Punct::Dot); + + Some(()) + } + + fn addendum(&mut self, entry: &mut EntryData) -> Option<()> { + let addendum = entry.text.remove(&TextField::Addendum)?; + self.builder + .push(Inline::Regular(addendum.text), Punct::Dot, Punct::Dot); + + Some(()) + } + + fn edition(&mut self, entry: &mut EntryData) -> Option<()> { + let text = match entry.number.remove(&NumberField::Edition)? { + NumberFieldData::Scalar(1) => "1st".to_string(), + NumberFieldData::Scalar(2) => "2nd".to_string(), + NumberFieldData::Scalar(3) => "3rd".to_string(), + NumberFieldData::Scalar(number) => format!("{}th", number), + NumberFieldData::Range(_, _) => return None, + NumberFieldData::Other(text) => text, + }; + + self.builder + .push(Inline::Regular(text), Punct::Dot, Punct::Dot); + Some(()) + } + + fn introduction(&mut self, entry: &mut EntryData) -> Option<()> { + let author = entry.author.remove(&AuthorField::Introduction)?; + self.builder.push( + Inline::Regular(format!("With an intro. by {}", author)), + Punct::Dot, + Punct::Dot, + ); + + Some(()) + } + + fn foreword(&mut self, entry: &mut EntryData) -> Option<()> { + let author = entry.author.remove(&AuthorField::Commentator)?; + self.builder.push( + Inline::Regular(format!("With a forew. by {}", author)), + Punct::Dot, + Punct::Dot, + ); + + Some(()) + } + + fn afterword(&mut self, entry: &mut EntryData) -> Option<()> { + let author = entry.author.remove(&AuthorField::Commentator)?; + self.builder.push( + Inline::Regular(format!("With an afterw. by {}", author)), + Punct::Dot, + Punct::Dot, + ); + + Some(()) + } + + fn note(&mut self, entry: &mut EntryData) -> Option<()> { + let note = entry.text.remove(&TextField::Note)?; + self.builder + .push(Inline::Regular(note.text), Punct::Dot, Punct::Comma); + + Some(()) + } + + fn pages(&mut self, entry: &mut EntryData) -> Option<()> { + let pages = entry.number.remove(&NumberField::Pages)?; + self.builder.push( + Inline::Regular(pages.to_string()), + Punct::Comma, + Punct::Space, + ); + + Some(()) + } + + fn page_total(&mut self, entry: &mut EntryData) -> Option<()> { + let page_total = entry.number.remove(&NumberField::PageTotal)?; + self.builder.push( + Inline::Regular(format!("{} pp", page_total)), + Punct::Dot, + Punct::Dot, + ); + + Some(()) + } + + fn journal(&mut self, entry: &mut EntryData) -> Option<()> { + let title = entry + .text + .remove(&TextField::Journal) + .or_else(|| entry.text.remove(&TextField::JournalTitle))?; + + let subtitle = entry + .text + .remove(&TextField::JournalSubtitle) + .map_or(String::new(), |data| format!(" {}", data.text)); + + self.builder.push( + Inline::Italic(titlecase(&format!("{}{}", title.text, subtitle))), + Punct::Dot, + Punct::Space, + ); + + Some(()) + } + + fn eid(&mut self, entry: &mut EntryData) -> Option<()> { + let eid = entry.text.remove(&TextField::Eid)?; + self.builder + .push(Inline::Regular(eid.text), Punct::Comma, Punct::Space); + + Some(()) + } + + fn isbn(&mut self, entry: &mut EntryData) -> Option<()> { + let isbn = entry.text.remove(&TextField::Isbn)?; + self.builder.push( + Inline::Regular("ISBN".to_string()), + Punct::Dot, + Punct::Colon, + ); + + self.builder + .push(Inline::Regular(isbn.text), Punct::Space, Punct::Dot); + + Some(()) + } + + fn issn(&mut self, entry: &mut EntryData) -> Option<()> { + let issn = entry.text.remove(&TextField::Issn)?; + self.builder.push( + Inline::Regular("ISSN".to_string()), + Punct::Dot, + Punct::Colon, + ); + + self.builder + .push(Inline::Regular(issn.text), Punct::Space, Punct::Dot); + + Some(()) + } + + fn url(&mut self, entry: &mut EntryData) -> Option<()> { + let url = entry.text.remove(&TextField::Url)?; + + self.builder + .push(Inline::Regular("URL".to_string()), Punct::Dot, Punct::Colon); + + let url = url.text; + let alt = url.clone(); + + self.builder + .push(Inline::Link { url, alt }, Punct::Space, Punct::Space); + + let date = entry.date.remove(&DateField::UrlDate)?; + self.builder.push( + Inline::Regular(format!("({date})")), + Punct::Space, + Punct::Space, + ); + + Some(()) + } + + fn doi(&mut self, entry: &mut EntryData) -> Option<()> { + let doi = entry.text.remove(&TextField::Doi)?; + self.builder + .push(Inline::Regular("DOI".to_string()), Punct::Dot, Punct::Colon); + + let alt = Url::parse(&doi.text) + .ok() + .filter(|url| !url.cannot_be_a_base()) + .map_or(doi.text, |url| url.path()[1..].to_string()); + + let url = format!("https://doi.org/{alt}"); + + self.builder + .push(Inline::Link { url, alt }, Punct::Space, Punct::Dot); + + Some(()) + } + + fn eprint(&mut self, entry: &mut EntryData) -> Option<()> { + let eprint = entry.text.remove(&TextField::Eprint)?; + let eprint_type = entry + .text + .remove(&TextField::EprintType) + .map_or_else(|| "eprint".to_string(), |data| data.text); + + if eprint_type.eq_ignore_ascii_case("arxiv") { + self.builder.push( + Inline::Regular("arXiv".to_string()), + Punct::Dot, + Punct::Colon, + ); + + self.builder.push( + Inline::Link { + url: format!("https://arxiv.org/abs/{}", eprint.text), + alt: eprint.text, + }, + Punct::Space, + Punct::Dot, + ); + } else { + self.builder + .push(Inline::Regular(eprint_type), Punct::Dot, Punct::Colon); + + self.builder + .push(Inline::Regular(eprint.text), Punct::Space, Punct::Dot); + } + + Some(()) + } + + pub fn finish(self) -> impl Iterator<Item = (Inline, Punct)> { + self.builder.finish() + } +} |