1 // This Source Code Form is subject to the terms of the Mozilla Public
2 // License, v. 2.0. If a copy of the MPL was not distributed with this
3 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 //
5 // Copyright © 2018 Corporation for Digital Scholarship
6 
7 use std::str::FromStr;
8 
9 use super::attr::{EnumGetAttribute, GetAttribute};
10 use super::error::*;
11 use super::version::Features;
12 use super::IsIndependent;
13 use super::Style;
14 
15 #[derive(Debug, Eq, Copy, Clone, PartialEq, EnumProperty, Hash)]
16 pub enum AnyVariable {
17     Ordinary(Variable),
18     Name(NameVariable),
19     Date(DateVariable),
20     Number(NumberVariable),
21 }
22 
23 impl FromStr for AnyVariable {
24     type Err = strum::ParseError;
from_str(s: &str) -> Result<Self, Self::Err>25     fn from_str(s: &str) -> Result<Self, Self::Err> {
26         use self::AnyVariable::*;
27         if let Ok(v) = Variable::from_str(s) {
28             return Ok(Ordinary(v));
29         } else if let Ok(v) = NameVariable::from_str(s) {
30             return Ok(Name(v));
31         } else if let Ok(v) = DateVariable::from_str(s) {
32             return Ok(Date(v));
33         } else if let Ok(v) = NumberVariable::from_str(s) {
34             return Ok(Number(v));
35         }
36         Err(strum::ParseError::VariantNotFound)
37     }
38 }
39 
40 impl EnumGetAttribute for Variable {}
41 impl EnumGetAttribute for NameVariable {}
42 impl EnumGetAttribute for NumberVariable {}
43 impl EnumGetAttribute for DateVariable {}
44 
45 impl GetAttribute for AnyVariable {
get_attr(s: &str, features: &Features) -> Result<Self, UnknownAttributeValue>46     fn get_attr(s: &str, features: &Features) -> Result<Self, UnknownAttributeValue> {
47         use self::AnyVariable::*;
48         if let Ok(v) = Variable::get_attr(s, features) {
49             return Ok(Ordinary(v));
50         } else if let Ok(v) = NameVariable::get_attr(s, features) {
51             return Ok(Name(v));
52         } else if let Ok(v) = DateVariable::get_attr(s, features) {
53             return Ok(Date(v));
54         } else if let Ok(v) = NumberVariable::get_attr(s, features) {
55             return Ok(Number(v));
56         }
57         Err(UnknownAttributeValue::new(s))
58     }
59 }
60 
61 /// Contrary to the CSL-M spec's declaration that number variables in a regular `<text variable>`
62 /// "should fail validation", that is perfectly valid, because "number variables are a subset of the
63 /// standard variables":
64 /// [Spec](https://docs.citationstyles.org/en/stable/specification.html#number-variables)
65 
66 #[derive(Debug, Eq, Copy, Clone, PartialEq, Hash)]
67 pub enum StandardVariable {
68     Ordinary(Variable),
69     Number(NumberVariable),
70 }
71 
72 impl From<&StandardVariable> for AnyVariable {
from(sv: &StandardVariable) -> Self73     fn from(sv: &StandardVariable) -> Self {
74         match sv {
75             StandardVariable::Number(n) => AnyVariable::Number(*n),
76             StandardVariable::Ordinary(o) => AnyVariable::Ordinary(*o),
77         }
78     }
79 }
80 
81 impl IsIndependent for StandardVariable {
is_independent(&self) -> bool82     fn is_independent(&self) -> bool {
83         match self {
84             StandardVariable::Number(n) => n.is_independent(),
85             StandardVariable::Ordinary(o) => o.is_independent(),
86         }
87     }
88 }
89 
90 impl GetAttribute for StandardVariable {
get_attr(s: &str, features: &Features) -> Result<Self, UnknownAttributeValue>91     fn get_attr(s: &str, features: &Features) -> Result<Self, UnknownAttributeValue> {
92         use self::StandardVariable::*;
93         if let Ok(v) = Variable::get_attr(s, features) {
94             return Ok(Ordinary(v));
95         } else if let Ok(v) = NumberVariable::get_attr(s, features) {
96             return Ok(Number(v));
97         }
98         Err(UnknownAttributeValue::new(s))
99     }
100 }
101 
102 impl IsIndependent for AnyVariable {
is_independent(&self) -> bool103     fn is_independent(&self) -> bool {
104         match self {
105             AnyVariable::Ordinary(ov) => ov.is_independent(),
106             AnyVariable::Number(nv) => nv.is_independent(),
107             _ => false,
108         }
109     }
110 }
111 
112 impl IsIndependent for Variable {
is_independent(&self) -> bool113     fn is_independent(&self) -> bool {
114         match self {
115             // Variable::CitationLabel is not independent, it just implies a YearSuffix
116             // which is, and that is handled in FreeCondWalker::text_variable()
117             Variable::LocatorExtra | Variable::YearSuffix | Variable::Hereinafter => true,
118             _ => false,
119         }
120     }
121 }
122 
123 #[derive(AsRefStr, EnumProperty, EnumString, Debug, Copy, Clone, PartialEq, Eq, Hash)]
124 #[strum(serialize_all = "kebab_case")]
125 #[non_exhaustive]
126 pub enum Variable {
127     /// Not sure where this is from, but it appears sometimes.
128     #[strum(serialize = "journalAbbreviation", serialize = "journal-abbreviation")]
129     JournalAbbreviation,
130     /// abstract of the item (e.g. the abstract of a journal article)
131     Abstract,
132     /// reader’s notes about the item content
133     Annote,
134     /// archive storing the item
135     Archive,
136     /// storage location within an archive (e.g. a box and folder number)
137     /// technically the spec says use an underscore, but that's probably a typo.
138     #[strum(serialize = "archive_location", serialize = "archive-location")]
139     ArchiveLocation,
140     /// geographic location of the archive,
141     ArchivePlace,
142     /// issuing or judicial authority (e.g. “USPTO” for a patent, “Fairfax Circuit Court” for a legal case)
143     /// CSL-M only
144     #[strum(props(csl = "1", cslM = "0"))]
145     Authority,
146     /// active={true} call number (to locate the item in a library)
147     CallNumber,
148     /// label identifying the item in in-text citations of label styles (e.g. “Ferr78”). May be assigned by the CSL processor based on item metadata.
149     CitationLabel,
150     /// title of the collection holding the item (e.g. the series title for a book)
151     CollectionTitle,
152     /// Not technically part of the spec, but https://forums.zotero.org/discussion/75366/accommodating-both-full-series-names-and-series-abbreviations
153     CollectionTitleShort,
154     /// title of the container holding the item (e.g. the book title for a book chapter, the journal title for a journal article)
155     ContainerTitle,
156     /// short/abbreviated form of “container-title” (also accessible through the “short” form of the “container-title” variable)
157     ContainerTitleShort,
158     /// physical (e.g. size) or temporal (e.g. running time) dimensions of the item
159     Dimensions,
160     /// Digital Object Identifier (e.g. “10.1128/AEM.02591-07”)
161     #[strum(serialize = "DOI", serialize = "doi")]
162     DOI,
163     /// name of the related event (e.g. the conference name when citing a conference paper)
164     Event,
165     /// geographic location of the related event (e.g. “Amsterdam, the Netherlands”)
166     EventPlace,
167 
168     /// class, type or genre of the item (e.g. “adventure” for an adventure movie, “PhD dissertation” for a PhD thesis)
169     Genre,
170     /// International Standard Book Number
171     #[strum(serialize = "ISBN", serialize = "isbn")]
172     ISBN,
173     /// International Standard Serial Number
174     #[strum(serialize = "ISSN", serialize = "issn")]
175     ISSN,
176     /// geographic scope of relevance (e.g. “US” for a US patent)
177     Jurisdiction,
178     /// keyword(s) or tag(s) attached to the item
179     Keyword,
180     /// medium description (e.g. “CD”, “DVD”, etc.)
181     Medium,
182     /// (short) inline note giving additional item details (e.g. a concise summary or commentary)
183     Note,
184     /// original publisher, for items that have been republished by a different publisher
185     OriginalPublisher,
186     /// geographic location of the original publisher (e.g. “London, UK”)
187     OriginalPublisherPlace,
188     /// title of the original version (e.g. “Война и мир”, the untranslated Russian title of “War and Peace”)
189     OriginalTitle,
190     /// PubMed Central reference number
191     #[strum(serialize = "PMCID", serialize = "pmcid")]
192     PMCID,
193     /// PubMed reference number
194     #[strum(serialize = "PMID", serialize = "pmid")]
195     PMID,
196     /// publisher
197     Publisher,
198     /// geographic location of the publisher
199     PublisherPlace,
200     /// resources related to the procedural history of a legal case
201     References,
202     /// title of the item reviewed by the current item
203     ReviewedTitle,
204     /// scale of e.g. a map
205     Scale,
206     /// container section holding the item (e.g. “politics” for a newspaper article).
207     /// TODO: CSL-M appears to interpret this as a number variable?
208     Section,
209     /// from whence the item originates (e.g. a library catalog or database)
210     Source,
211     /// (publication) status of the item (e.g. “forthcoming”)
212     Status,
213     /// primary title of the item
214     Title,
215     /// short/abbreviated form of “title” (also accessible through the “short” form of the “title” variable)
216     #[strum(serialize = "title-short", serialize = "shortTitle")]
217     TitleShort,
218     ///  URL (e.g. “https://aem.asm.org/cgi/content/full/74/9/2766”)
219     #[strum(serialize = "URL", serialize = "url")]
220     URL,
221     /// version of the item (e.g. “2.0.9” for a software program)
222     Version,
223     /// disambiguating year suffix in author-date styles (e.g. “a” in “Doe, 1999a”)
224     YearSuffix,
225 
226     // These are in the CSL-JSON spec
227     CitationKey,
228     Division,
229     EventTitle,
230     PartTitle,
231     ReviewedGenre,
232     #[strum(serialize = "archive-collection", serialize = "archive_collection")]
233     ArchiveCollection,
234     VolumeTitleShort,
235 
236     /// CSL-M only
237     // Intercept Hereinafter at CiteContext, as it isn't known at Reference-time.
238     // Global-per-document config should be its own thing separate from references.
239     // TODO: delete any noRef="true" and replace with serde directives not to read from
240     // CSL-JSON.
241     #[strum(props(csl = "0", cslM = "1", noRef = "true"))]
242     Hereinafter,
243     /// CSL-M only
244     #[strum(props(csl = "0", cslM = "1"))]
245     Dummy,
246     /// CSL-M only
247     #[strum(props(csl = "0", cslM = "1"))]
248     LocatorExtra,
249     /// CSL-M only
250     #[strum(props(csl = "0", cslM = "1"))]
251     VolumeTitle,
252 
253     /// CSL-M only
254     ///
255     /// Not documented in the CSL-M spec.
256     #[strum(props(csl = "0", cslM = "1"))]
257     Committee,
258 
259     /// CSL-M only
260     ///
261     /// Not documented in the CSL-M spec. See [Indigo Book][ib] section 'R26. Short Form
262     /// Citation for Court Documents' for its intended use case, and the Juris-M [US cheat
263     /// sheet][uscs]
264     ///
265     /// [uscs]: https://juris-m.github.io/cheat-sheets/us.pdf
266     ///
267     /// [ib]: https://law.resource.org/pub/us/code/blue/IndigoBook.html
268     #[strum(props(csl = "0", cslM = "1"))]
269     DocumentName,
270 
271     /// CSL-M only
272     ///
273     /// Not documented in the CSL-M spec.
274     ///
275     /// TODO: I think variable="gazette-flag" may have been superseded by type="gazette",
276     /// but clearly you can still tick the "Gazette Ref" checkbox in Juris-M on a statute.
277     /// Ask Frank. See also https://juris-m.github.io/cheat-sheets/us.pdf
278     #[strum(props(csl = "0", cslM = "1"))]
279     GazetteFlag,
280 
281     // TODO: should not be accessible in condition blocks
282     Language,
283 }
284 
285 impl Variable {
should_replace_hyphens(self) -> bool286     pub fn should_replace_hyphens(self) -> bool {
287         false
288     }
hyperlink(self, value: &str) -> Option<&str>289     pub fn hyperlink(self, value: &str) -> Option<&str> {
290         match self {
291             Variable::URL => Some(value),
292             Variable::DOI => Some(value),
293             _ => None,
294         }
295     }
296 }
297 
298 impl IsIndependent for NumberVariable {
is_independent(&self) -> bool299     fn is_independent(&self) -> bool {
300         match self {
301             NumberVariable::Locator => true,
302             NumberVariable::FirstReferenceNoteNumber => true,
303             _ => false,
304         }
305     }
306 }
307 
308 #[derive(AsRefStr, EnumProperty, EnumString, Debug, Copy, Clone, PartialEq, Eq, Hash)]
309 #[strum(serialize_all = "kebab_case")]
310 #[non_exhaustive]
311 pub enum NumberVariable {
312     ChapterNumber,
313     CollectionNumber,
314     Edition,
315     Issue,
316     Number,
317     NumberOfPages,
318     NumberOfVolumes,
319     Volume,
320 
321     /// Locator, Page and PageFirst, FRRN, and CiteNumber: These are technically meant to be standard variables in CSL 1.0.1, but the spec
322     /// requires us to treat them as numerics for `<label plural="contextual">` anyway.
323     ///
324     /// a cite-specific pinpointer within the item (e.g. a page number within a book, or a volume in a multi-volume work). Must be accompanied in the input data by a label indicating the locator type (see the Locators term list), which determines which term is rendered by cs:label when the “locator” variable is selected.
325     Locator,
326 
327     /// range of pages the item (e.g. a journal article) covers in a container (e.g. a journal issue)
328     Page,
329     /// first page of the range of pages the item (e.g. a journal article) covers in a container (e.g. a journal issue)
330     PageFirst,
331 
332     /// number of a preceding note containing the first reference to the item. Assigned by the CSL processor. The variable holds no value for non-note-based styles, or when the item hasn’t been cited in any preceding notes.
333     FirstReferenceNoteNumber,
334 
335     /// index (starting at 1) of the cited reference in the bibliography (generated by the CSL processor)
336     CitationNumber,
337 
338     /// CSL-M only
339     #[strum(props(csl = "0", cslM = "1"))]
340     PublicationNumber,
341 
342     /// CSL-M only
343     #[strum(props(csl = "0", cslM = "1"))]
344     Supplement,
345 
346     /// CSL-M only
347     #[strum(props(csl = "0", cslM = "1"))]
348     Authority,
349 
350     // From CSL-JSON schema
351     Part,
352     Printing,
353 }
354 
355 impl NumberVariable {
should_replace_hyphens(self, style: &Style) -> bool356     pub fn should_replace_hyphens(self, style: &Style) -> bool {
357         match self {
358             NumberVariable::Locator => true,
359             NumberVariable::Page => style.page_range_format.is_some(),
360             _ => false,
361         }
362     }
is_quantity(self) -> bool363     pub fn is_quantity(self) -> bool {
364         match self {
365             NumberVariable::NumberOfVolumes => true,
366             NumberVariable::NumberOfPages => true,
367             _ => false,
368         }
369     }
370 }
371 
372 #[derive(
373     AsRefStr, EnumProperty, EnumString, Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd,
374 )]
375 #[strum(serialize_all = "kebab_case")]
376 #[non_exhaustive]
377 pub enum NameVariable {
378     /// author
379     Author,
380     /// editor of the collection holding the item (e.g. the series editor for a book)
381     CollectionEditor,
382     /// composer (e.g. of a musical score)
383     Composer,
384     /// author of the container holding the item (e.g. the book author for a book chapter)
385     ContainerAuthor,
386     /// director (e.g. of a film)
387     Director,
388     /// editor
389     Editor,
390     /// managing editor (“Directeur de la Publication” in French)
391     EditorialDirector,
392     /// illustrator (e.g. of a children’s book)
393     Illustrator,
394     /// interviewer (e.g. of an interview)
395     Interviewer,
396     /// ?
397     OriginalAuthor,
398     /// recipient (e.g. of a letter)
399     Recipient,
400     /// author of the item reviewed by the current item
401     ReviewedAuthor,
402     /// translator
403     Translator,
404 
405     #[strum(
406         serialize = "editortranslator",
407         props(feature = "var_editortranslator")
408     )]
409     EditorTranslator,
410 
411     /// CSL-M only
412     #[strum(props(csl = "0", cslM = "1"))]
413     Authority,
414 
415     /// CSL-M only
416     ///
417     /// The dummy name variable is always empty. Use it to force all name variables called through
418     /// a cs:names node to render through cs:substitute, and so suppress whichever is chosen for
419     /// rendering to be suppressed through the remainder of the current cite.
420     #[strum(props(csl = "0", cslM = "1"))]
421     Dummy,
422 
423     // From the CSL-JSON schema
424 
425     Curator,
426     ScriptWriter,
427     Performer,
428     Producer,
429     ExecutiveProducer,
430     Guest,
431     Narrator,
432     Chair,
433     Compiler,
434     Contributor,
435     SeriesCreator,
436     Organizer,
437     Host,
438 }
439 
440 impl Default for NameVariable {
default() -> Self441     fn default() -> Self {
442         NameVariable::Dummy
443     }
444 }
445 
446 #[derive(AsRefStr, EnumProperty, EnumString, Debug, Copy, Clone, PartialEq, Eq, Hash)]
447 #[strum(serialize_all = "kebab_case")]
448 #[non_exhaustive]
449 pub enum DateVariable {
450     /// date the item has been accessed
451     Accessed,
452     /// ?
453     Container,
454     /// date the related event took place
455     EventDate,
456     /// date the item was issued/published
457     Issued,
458     /// (issue) date of the original version
459     OriginalDate,
460     /// date the item (e.g. a manuscript) has been submitted for publication
461     Submitted,
462     /// CSL-M only
463     #[strum(props(csl = "0", cslM = "1"))]
464     LocatorDate,
465     /// CSL-M only
466     #[strum(props(csl = "0", cslM = "1"))]
467     PublicationDate,
468     /// CSL-M only
469     #[strum(props(csl = "0", cslM = "1"))]
470     AvailableDate,
471 }
472