diff --git a/lib/cff/formatters/bibtex.rb b/lib/cff/formatters/bibtex.rb index 230a621..3ce2095 100644 --- a/lib/cff/formatters/bibtex.rb +++ b/lib/cff/formatters/bibtex.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -# Copyright (c) 2018-2023 The Ruby Citation File Format Developers. +# Copyright (c) 2018-2024 The Ruby Citation File Format Developers. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -45,13 +45,17 @@ class BibTeX < Formatter # :nodoc: month.downcase unless month.nil? end.freeze + # We need to escape these characters in titles and names, as per + # https://tex.stackexchange.com/questions/34580/escape-character-in-latex + ESCAPE_CHARS = Regexp.new(/([&%$#_{}])/) + def self.format(model:, preferred_citation: true) # rubocop:disable Metrics model = select_and_check_model(model, preferred_citation) return if model.nil? values = {} values['author'] = actor_list(model.authors) - values['title'] = "{#{model.title}}" + values['title'] = "{#{l(model.title)}}" publication_type = bibtex_type(model) publication_data_from_model(model, publication_type, values) @@ -79,7 +83,7 @@ def self.format(model:, preferred_citation: true) # rubocop:disable Metrics def self.publication_data_from_model(model, type, fields) ENTRY_TYPE_MAP[type].each do |field| if model.respond_to?(field) - fields[field] = model.send(field).to_s + fields[field] = l(model.send(field).to_s) else field = field.chomp('!') fields[field] = send("#{field}_from_model", model) @@ -108,9 +112,9 @@ def self.address_from_model(model) # BibTeX 'institution' could be grabbed from an author's affiliation, or # provided explicitly. def self.institution_from_model(model) - return model.institution.name unless model.institution.empty? + return l(model.institution.name) unless model.institution.empty? - model.authors.first.affiliation + l(model.authors.first.affiliation) end # BibTeX 'school' is CFF 'institution'. @@ -125,7 +129,7 @@ def self.type_from_model(model) # BibTeX 'booktitle' is CFF 'collection-title'. def self.booktitle_from_model(model) - model.collection_title + l(model.collection_title) end # BibTeX 'editor' is CFF 'editors' or 'editors-series'. @@ -138,11 +142,11 @@ def self.editor_from_model(model) end def self.publisher_from_model(model) - model.publisher.empty? ? '' : model.publisher.name + model.publisher.empty? ? '' : l(model.publisher.name) end def self.series_from_model(model) - model.conference.empty? ? '' : model.conference.name + model.conference.empty? ? '' : l(model.conference.name) end # If we're citing a conference paper, try and use the date of the @@ -182,9 +186,9 @@ def self.bibtex_type(model) # rubocop:disable Metrics/CyclomaticComplexity end end - def self.format_actor(author) - return "{#{author.name}}" if author.is_a?(Entity) - return author.alias if author.family_names.empty? && author.given_names.empty? + def self.format_actor(author) # rubocop:disable Metrics/AbcSize + return "{#{l(author.name)}}" if author.is_a?(Entity) + return l(author.alias) if author.family_names.empty? && author.given_names.empty? particle = author.name_particle.empty? ? '' : "#{author.name_particle} " @@ -209,6 +213,12 @@ def self.generate_citekey(fields) Util.parameterize(reference) end + + # Escape a string to preserve special characters in LaTeX output. + # Used in many places, so short method name to preserve reading flow. + def self.l(string) + string.gsub(ESCAPE_CHARS, '\\\\\1') + end end end end diff --git a/test/files/formatted/preferred-citation-conference-paper-with-escapes.apa b/test/files/formatted/preferred-citation-conference-paper-with-escapes.apa new file mode 100644 index 0000000..143a9e8 --- /dev/null +++ b/test/files/formatted/preferred-citation-conference-paper-with-escapes.apa @@ -0,0 +1 @@ +Haines, R., & The {curly_braces} Collective. (2021). CFF is #1, 100% [Conference paper]. Software Credit for Fun & Profit ($$$). https://doi.org/10.5281/zenodo.1184077 diff --git a/test/files/formatted/preferred-citation-conference-paper-with-escapes.bibtex b/test/files/formatted/preferred-citation-conference-paper-with-escapes.bibtex new file mode 100644 index 0000000..a062bc3 --- /dev/null +++ b/test/files/formatted/preferred-citation-conference-paper-with-escapes.bibtex @@ -0,0 +1,11 @@ +@inproceedings{Haines_CFF_is_1_2021, +author = {Haines, Robert and {The \{curly\_braces\} Collective}}, +booktitle = {Software Credit for Fun \& Profit (\$\$\$)}, +doi = {10.5281/zenodo.1184077}, +month = sep, +publisher = {The CFF \& Friends}, +series = {Software Credit \#1}, +title = {{CFF is \#1, 100\%}}, +url = {https://github.com/citation-file-format/ruby-cff}, +year = {2021} +} diff --git a/test/files/formatted/preferred-citation-journal-with-escapes.apa b/test/files/formatted/preferred-citation-journal-with-escapes.apa new file mode 100644 index 0000000..2c6b641 --- /dev/null +++ b/test/files/formatted/preferred-citation-journal-with-escapes.apa @@ -0,0 +1 @@ +Haines, R. (2024). CFF for the $%&#_{} win. Journal of Maths & Philosophy, 1(1) diff --git a/test/files/formatted/preferred-citation-journal-with-escapes.bibtex b/test/files/formatted/preferred-citation-journal-with-escapes.bibtex new file mode 100644 index 0000000..3fc79ed --- /dev/null +++ b/test/files/formatted/preferred-citation-journal-with-escapes.bibtex @@ -0,0 +1,9 @@ +@article{Haines_CFF_for_the_2024, +author = {Haines, Robert}, +journal = {Journal of Maths \& Philosophy}, +month = jan, +number = {1}, +title = {{CFF for the \$\%\&\#\_\{\} win}}, +volume = {1}, +year = {2024} +} diff --git a/test/files/formatted/software-with-escapes.apa b/test/files/formatted/software-with-escapes.apa new file mode 100644 index 0000000..d93323f --- /dev/null +++ b/test/files/formatted/software-with-escapes.apa @@ -0,0 +1 @@ +Haines, R., & The {curly_braces} Collective. (2024). Software that uses the following symbols: &, %, $, # (Version 0.0.1-alpha) [Computer software] diff --git a/test/files/formatted/software-with-escapes.bibtex b/test/files/formatted/software-with-escapes.bibtex new file mode 100644 index 0000000..a3aab55 --- /dev/null +++ b/test/files/formatted/software-with-escapes.bibtex @@ -0,0 +1,7 @@ +@software{Haines_Software_that_uses_2024, +author = {Haines, Robert and {The \{curly\_braces\} Collective}}, +month = jan, +title = {{Software that uses the following symbols: \&, \%, \$, \#}}, +version = {0.0.1-alpha}, +year = {2024} +} diff --git a/test/files/formatter/preferred-citation-conference-paper-with-escapes.cff b/test/files/formatter/preferred-citation-conference-paper-with-escapes.cff new file mode 100644 index 0000000..61276a2 --- /dev/null +++ b/test/files/formatter/preferred-citation-conference-paper-with-escapes.cff @@ -0,0 +1,36 @@ +# A report preferred-citation + +cff-version: 1.2.0 +message: If you use this software in your work, please cite it using the following metadata +title: Ruby CFF Library +authors: +- family-names: Haines + given-names: Robert + affiliation: The University of Manchester +keywords: +- ruby +- credit +- citation +- cff +version: 0.9.0 +date-released: 2021-09-21 +license: Apache-2.0 +repository-artifact: https://rubygems.org/gems/cff +type: software +preferred-citation: + type: conference-paper + conference: + name: "Software Credit #1" + title: "CFF is #1, 100%" + authors: + - family-names: Haines + given-names: Robert + affiliation: The University of Manchester + - name: "The {curly_braces} Collective" + publisher: + name: The CFF & Friends + collection-title: Software Credit for Fun & Profit ($$$) + month: 9 + year: 2021 + url: https://github.com/citation-file-format/ruby-cff + doi: 10.5281/zenodo.1184077 diff --git a/test/files/formatter/preferred-citation-journal-with-escapes.cff b/test/files/formatter/preferred-citation-journal-with-escapes.cff new file mode 100644 index 0000000..99fc2cc --- /dev/null +++ b/test/files/formatter/preferred-citation-journal-with-escapes.cff @@ -0,0 +1,23 @@ +# Note this is a made up entry. + +cff-version: 1.2.0 +message: If you use this software in your work, please cite it using the following metadata +title: Ruby-CFF +authors: +- family-names: Haines + given-names: Robert +version: 0.1 +date-released: 2024-01-16 + +# JOSS paper as preferred citation +preferred-citation: + authors: + - family-names: Haines + given-names: Robert + journal: Journal of Maths & Philosophy + month: 1 + title: "CFF for the $%&#_{} win" + type: article + volume: 1 + issue: 1 + year: 2024 diff --git a/test/files/formatter/software-with-escapes.cff b/test/files/formatter/software-with-escapes.cff new file mode 100644 index 0000000..13b633d --- /dev/null +++ b/test/files/formatter/software-with-escapes.cff @@ -0,0 +1,9 @@ +cff-version: 1.2.0 +message: "If you use this software, please cite it as below." +authors: + - family-names: Haines + given-names: Robert + - name: "The {curly_braces} Collective" +title: "Software that uses the following symbols: &, %, $, #" +version: 0.0.1-alpha +date-released: 2024-01-16