diff options
author | Norbert Preining <norbert@preining.info> | 2021-05-23 03:00:39 +0000 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2021-05-23 03:00:39 +0000 |
commit | f1261b349e875b842745b63258c3e338cb1fe3bf (patch) | |
tree | b5d402b3e80818cde2c079a42249f3dcb9732247 /support/texlab/src/citation.rs | |
parent | 58aa1ac09b1d9e4769d0a0661cf12e2b2db41b14 (diff) |
CTAN sync 202105230300
Diffstat (limited to 'support/texlab/src/citation.rs')
-rw-r--r-- | support/texlab/src/citation.rs | 165 |
1 files changed, 165 insertions, 0 deletions
diff --git a/support/texlab/src/citation.rs b/support/texlab/src/citation.rs new file mode 100644 index 0000000000..e185c14b55 --- /dev/null +++ b/support/texlab/src/citation.rs @@ -0,0 +1,165 @@ +mod bibutils; +mod name; +mod ris; + +use std::sync::Arc; + +use citeproc::{prelude::SupportedFormat, ClusterPosition, Processor}; +use citeproc_db::PredefinedLocales; +use citeproc_io::{Cite, Cluster, Reference}; +use lsp_types::{MarkupContent, MarkupKind}; +use once_cell::sync::Lazy; +use regex::Regex; + +use crate::syntax::{bibtex, CstNode}; + +use self::{ + bibutils::*, + ris::{RisLibrary, RisReference}, +}; + +static APA_STYLE: &str = include_str!("citation/apa.csl"); + +static DOI_URL_PATTERN: &str = r#"https://doi.org/\[.*\]\(.*\)"#; + +static DOI_URL_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(DOI_URL_PATTERN).unwrap()); + +pub fn render_citation(root: &bibtex::SyntaxNode, key: &str) -> Option<MarkupContent> { + let ris_reference = convert_to_ris(root, key)?; + let doi_url = get_doi_url_markdown(&ris_reference); + let csl_reference: Reference = ris_reference.into(); + let html = generate_bibliography(csl_reference)?; + + let mut markdown = html2md::parse_html(&html).trim().to_owned(); + if markdown == "" { + return None; + } + + if let Some(doi_url) = doi_url { + markdown = DOI_URL_REGEX + .replace(&markdown, doi_url.as_str()) + .into_owned(); + } + + markdown = markdown + .replace("..", ".") + .replace("\\\'", "'") + .replace("\\-", "-") + .replace("\\\\textsubscript", "") + .replace("\\\\textsuperscript", ""); + let content = MarkupContent { + kind: MarkupKind::Markdown, + value: markdown, + }; + Some(content) +} + +fn convert_to_ris(root: &bibtex::SyntaxNode, key: &str) -> Option<RisReference> { + let mut bib_code = String::new(); + for string in root + .children() + .filter_map(bibtex::String::cast) + .filter(|string| string.name().is_some()) + { + bib_code.push_str(&string.syntax().to_string()); + } + + let entry = root + .children() + .filter_map(bibtex::Entry::cast) + .find(|entry| entry.key().map(|key| key.text()) == Some(key))?; + bib_code.push_str(&entry.syntax().to_string()); + + bib_code = bib_code.replace("\\hypen", "-"); + + let ris_code = bibutils::convert(&bib_code, InputFormat::Biblatex, OutputFormat::Ris)?; + let ris_lib = RisLibrary::parse(ris_code.lines()); + ris_lib + .references + .into_iter() + .find(|reference| reference.id.as_ref().map(AsRef::as_ref) == Some(key)) +} + +fn get_doi_url_markdown(ris_reference: &RisReference) -> Option<String> { + ris_reference + .doi + .as_ref() + .map(|doi| format!("[doi:{}](https://doi.org/{})", doi, doi)) +} + +fn generate_bibliography(reference: Reference) -> Option<String> { + let locales = Arc::new(PredefinedLocales::bundled_en_us()); + let mut processor = Processor::new(APA_STYLE, locales, false, SupportedFormat::Html).unwrap(); + let cite = Cite::basic(&reference.id); + let cluster = Cluster { + id: 1, + cites: vec![cite], + }; + processor.insert_reference(reference); + processor.init_clusters(vec![cluster]); + processor + .set_cluster_order(&[ClusterPosition { + id: 1, + note: Some(1), + }]) + .unwrap(); + processor.get_bibliography().pop() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_simple() { + let document = bibtex::parse( + r#" + @article{foo, + author = {Foo Bar}, + title = {Baz Qux}, + year = {2020} + } + "#, + ); + + let actual_md = render_citation(&document.root, "foo").unwrap(); + + let expected_md = MarkupContent { + kind: MarkupKind::Markdown, + value: "Bar, F. (2020). *Baz Qux*.".into(), + }; + + assert_eq!(actual_md, expected_md); + } + + #[test] + fn test_string() { + let document = bibtex::parse( + r#" + @string{author = "Foo Bar"} + @article{foo, + author = author, + title = {Baz Qux}, + year = {2020} + } + "#, + ); + let actual_md = render_citation(&document.root, "foo").unwrap(); + + let expected_md = MarkupContent { + kind: MarkupKind::Markdown, + value: "Bar, F. (2020). *Baz Qux*.".into(), + }; + + assert_eq!(actual_md, expected_md); + } + + #[test] + fn test_unknown_key() { + let document = bibtex::parse(""); + + let actual_md = render_citation(&document.root, "foo"); + + assert_eq!(actual_md, None); + } +} |