use biblatex::Person;
use biblatex::{Entry, EntryType};
use std::collections::HashMap;
use utils::BiblatexUtils;
use validators::{MatchedCitation, MatchedCitationDisambiguated};
use crate::utils;
use crate::validators;
use crate::validators::ArticleFileData;
pub fn entries_to_strings(entries: &Vec<MatchedCitationDisambiguated>) -> Vec<String> {
let sorted_entries = sort_entries(entries);
let mut strings_output: Vec<String> = Vec::new();
for matched_citation in sorted_entries {
match matched_citation.entry.entry_type {
EntryType::Book => {
strings_output.push(transform_book_entry(&matched_citation));
}
EntryType::Article => strings_output.push(transform_article_entry(&matched_citation)),
_ => println!(
"Entry type not supported: {:?}",
&matched_citation.entry.entry_type
),
}
}
strings_output
}
pub fn transform_keys_to_citations(article_file_data: &ArticleFileData) -> String {
let mut full_content = article_file_data.full_file_content.clone();
for matched_citation in &article_file_data.entries_disambiguated {
if matched_citation.citation_raw.starts_with('@') {
full_content = full_content.replace(
&matched_citation.citation_raw,
&matched_citation.citation_author_date_disambiguated,
);
}
}
full_content
}
pub fn disambiguate_matched_citations(
citations: Vec<MatchedCitation>,
) -> Vec<MatchedCitationDisambiguated> {
let mut author_year_groups: HashMap<String, Vec<&MatchedCitation>> = HashMap::new();
for citation in &citations {
let author = citation.entry.author().unwrap();
let author_last_name = author[0].name.clone();
let date = citation.entry.date().unwrap();
let year =
BiblatexUtils::extract_year_from_date(&date, citation.entry.key.clone()).unwrap();
let author_year_key = format!("{}-{}", author_last_name, year);
author_year_groups
.entry(author_year_key)
.or_insert_with(Vec::new)
.push(citation);
}
let mut citation_to_disambiguated: HashMap<String, String> = HashMap::new();
let mut year_to_disambiguated: HashMap<String, String> = HashMap::new();
for (_author_year_key, group_citations) in author_year_groups {
if group_citations.len() > 1 {
let mut sorted_citations = group_citations;
sorted_citations.sort_by(|a, b| a.entry.key.cmp(&b.entry.key));
for (index, citation) in sorted_citations.iter().enumerate() {
let letter = char::from(b'a' + index as u8);
let disambiguated = create_disambiguated_citation(letter, &citation.entry);
citation_to_disambiguated.insert(citation.citation_raw.clone(), disambiguated);
let disambiguated_year = create_disambiguated_year(letter, &citation.entry);
year_to_disambiguated.insert(citation.citation_raw.clone(), disambiguated_year);
}
} else {
let citation = group_citations[0];
let standard = create_standard_citation(&citation.citation_raw, &citation.entry);
citation_to_disambiguated.insert(citation.citation_raw.clone(), standard);
}
}
citations
.into_iter()
.map(|matched_citation| {
let disambiguated = citation_to_disambiguated
.get(&matched_citation.citation_raw)
.cloned()
.unwrap_or_else(|| matched_citation.citation_raw.clone());
let disambiguated_year = year_to_disambiguated
.get(&matched_citation.citation_raw)
.cloned()
.unwrap_or_else(|| extract_date(&matched_citation.entry).to_string());
MatchedCitationDisambiguated {
citation_raw: matched_citation.citation_raw,
citation_author_date_disambiguated: disambiguated,
year_disambiguated: disambiguated_year,
entry: matched_citation.entry,
}
})
.collect()
}
fn transform_book_entry(matched_citation: &MatchedCitationDisambiguated) -> String {
let mut book_string = String::new();
let author = matched_citation.entry.author().unwrap();
let year = matched_citation.year_disambiguated.clone();
let title = extract_title(&matched_citation.entry);
let publisher = extract_publisher(&matched_citation.entry);
let address = extract_address(&matched_citation.entry);
let translators = matched_citation.entry.translator().unwrap_or(Vec::new());
let doi = matched_citation.entry.doi().unwrap_or("".to_string());
add_authors(author, &mut book_string);
add_year(year, &mut book_string);
add_book_title(title, &mut book_string);
add_translators(translators, &mut book_string);
add_address_and_publisher(address, publisher, &mut book_string);
add_doi(doi, &mut book_string);
book_string.trim_end().to_string()
}
fn transform_article_entry(matched_citation: &MatchedCitationDisambiguated) -> String {
let mut article_string = String::new();
let author = matched_citation.entry.author().unwrap();
let year = matched_citation.year_disambiguated.clone();
let title = extract_title(&matched_citation.entry);
let journal = extract_journal(&matched_citation.entry);
let volume = extract_volume(&matched_citation.entry);
let number = extract_number(&matched_citation.entry);
let pages = extract_pages(&matched_citation.entry);
let translators = matched_citation.entry.translator().unwrap_or(Vec::new());
let doi = matched_citation.entry.doi().unwrap_or("".to_string());
add_authors(author, &mut article_string);
add_year(year, &mut article_string);
add_article_title(title, &mut article_string);
add_journal_volume_number_pages(journal, volume, number, pages, &mut article_string);
add_translators(translators, &mut article_string);
add_doi(doi, &mut article_string);
article_string.trim_end().to_string()
}
fn generate_contributors(
contributors: Vec<biblatex::Person>,
contributor_description: String,
) -> String {
let mut contributors_str = String::new();
if contributors.len() > 1 {
contributors_str.push_str(&format!("{} by ", contributor_description));
for (i, person) in contributors.iter().enumerate() {
if i == contributors.len() - 1 {
contributors_str.push_str(&format!("and {} {}. ", person.given_name, person.name));
} else {
contributors_str.push_str(&format!("{} {}, ", person.given_name, person.name));
}
}
} else if contributors.len() == 1 {
contributors_str.push_str(&format!(
"{} by {} {}. ",
contributor_description, contributors[0].given_name, contributors[0].name
));
}
contributors_str
}
fn add_year(year: String, target_string: &mut String) {
target_string.push_str(&format!("{}. ", year));
}
fn add_authors(author: Vec<biblatex::Person>, bib_html: &mut String) {
bib_html.push_str(&format_authors(author))
}
fn format_authors(author: Vec<biblatex::Person>) -> String {
if author.len() > 2 {
return format!("{}, {} et al. ", author[0].name, author[0].given_name);
} else if author.len() == 2 {
return format!(
"{}, {} and {} {}. ",
author[0].name, author[0].given_name, author[1].given_name, author[1].name
);
} else {
return format!("{}, {}. ", author[0].name, author[0].given_name);
}
}
fn format_authors_last_name_only(author: Vec<biblatex::Person>) -> String {
if author.len() > 2 {
return format!("{} et al.", author[0].name);
} else if author.len() == 2 {
return format!("{} and {}", author[0].name, author[1].name);
} else {
return format!("{}", author[0].name);
}
}
fn add_translators(translators: Vec<biblatex::Person>, target_string: &mut String) {
let translators_mdx = generate_contributors(translators, "Translated".to_string());
if !translators_mdx.is_empty() {
target_string.push_str(&translators_mdx);
}
}
fn add_doi(doi: String, target_string: &mut String) {
if !doi.is_empty() {
target_string.push_str(&format!(" https://doi.org/{}.", doi));
}
}
fn add_book_title(title: String, target_string: &mut String) {
target_string.push_str(&format!("_{}_. ", title));
}
fn add_article_title(title: String, target_string: &mut String) {
target_string.push_str(&format!("\"{}\". ", title));
}
fn add_address_and_publisher(address: String, publisher: String, target_string: &mut String) {
target_string.push_str(&format!("{}: {}. ", address, publisher));
}
fn add_journal_volume_number_pages(
journal: String,
volume: i64,
number: String,
pages: String,
target_string: &mut String,
) {
target_string.push_str(&format!(
"_{}_ {} ({}): {}. ",
journal, volume, number, pages
));
}
fn sort_entries(entries: &Vec<MatchedCitationDisambiguated>) -> Vec<MatchedCitationDisambiguated> {
let mut sorted_entries = entries.clone();
sorted_entries.sort_by(|a, b| {
let a_authors = a.entry.author().unwrap_or_default();
let b_authors = b.entry.author().unwrap_or_default();
let a_author_key = author_key(&a_authors);
let b_author_key = author_key(&b_authors);
let cmp_author = a_author_key.cmp(&b_author_key);
if cmp_author != std::cmp::Ordering::Equal {
return cmp_author;
}
let a_year = &a.year_disambiguated;
let b_year = &b.year_disambiguated;
let cmp_year = a_year.cmp(&b_year);
if cmp_year != std::cmp::Ordering::Equal {
return cmp_year;
}
let a_title = extract_title(&a.entry).to_lowercase();
let b_title = extract_title(&b.entry).to_lowercase();
a_title.cmp(&b_title)
});
sorted_entries
}
fn author_key(authors: &Vec<Person>) -> String {
authors
.first()
.map(|p| p.name.clone().to_lowercase())
.unwrap_or_default()
}
fn extract_title(entry: &Entry) -> String {
let title_spanned = entry.title().unwrap();
let title = BiblatexUtils::extract_spanned_chunk(title_spanned);
title
}
fn extract_publisher(entry: &Entry) -> String {
let publisher_spanned = entry.publisher().unwrap();
let publisher = BiblatexUtils::extract_publisher(&publisher_spanned);
publisher
}
fn extract_address(entry: &Entry) -> String {
let address_spanned = entry.address().unwrap();
let address = BiblatexUtils::extract_spanned_chunk(address_spanned);
address
}
fn extract_date(entry: &Entry) -> i32 {
let date = entry.date().unwrap();
let year = BiblatexUtils::extract_year_from_date(&date, entry.key.clone()).unwrap();
year
}
fn extract_journal(entry: &Entry) -> String {
let journal_spanned = entry.journal().unwrap();
let journal = BiblatexUtils::extract_spanned_chunk(&journal_spanned);
journal
}
fn extract_volume(entry: &Entry) -> i64 {
let volume_permissive = entry.volume().unwrap();
let volume = BiblatexUtils::extract_volume(&volume_permissive);
volume
}
fn extract_number(entry: &Entry) -> String {
let number_spanned = entry.number().unwrap();
let number = BiblatexUtils::extract_spanned_chunk(&number_spanned);
number
}
fn extract_pages(entry: &Entry) -> String {
let pages_permissive = entry.pages().unwrap();
let pages = BiblatexUtils::extract_pages(&pages_permissive);
pages
}
fn create_disambiguated_citation(letter: char, entry: &Entry) -> String {
let author = format_authors_last_name_only(entry.author().unwrap());
let year = extract_date(entry);
format!("{} {}{}", author, year, letter)
}
fn create_disambiguated_year(letter: char, entry: &Entry) -> String {
let year = extract_date(entry);
format!("{}{}", year, letter)
}
fn create_standard_citation(raw_citation: &str, entry: &Entry) -> String {
if raw_citation.starts_with('@') {
let author = entry.author().unwrap();
let author_last_name = author[0].name.clone();
let date = entry.date().unwrap();
let year = BiblatexUtils::extract_year_from_date(&date, entry.key.clone()).unwrap();
format!("{} {}", author_last_name, year)
} else {
raw_citation.to_string()
}
}