1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright © 2018 Corporation for Digital Scholarship

use super::attr::GetAttribute;
use super::error::*;
use super::version::Features;

#[derive(Debug, Eq, Copy, Clone, PartialEq, EnumProperty)]
pub enum AnyVariable {
    Ordinary(Variable),
    Name(NameVariable),
    Date(DateVariable),
    Number(NumberVariable),
}

impl GetAttribute for AnyVariable {
    fn get_attr(s: &str, features: &Features) -> Result<Self, UnknownAttributeValue> {
        use self::AnyVariable::*;
        if let Ok(v) = Variable::get_attr(s, features) {
            return Ok(Ordinary(v));
        } else if let Ok(v) = NameVariable::get_attr(s, features) {
            return Ok(Name(v));
        } else if let Ok(v) = DateVariable::get_attr(s, features) {
            return Ok(Date(v));
        } else if let Ok(v) = NumberVariable::get_attr(s, features) {
            return Ok(Number(v));
        }
        Err(UnknownAttributeValue::new(s))
    }
}

/// Contrary to the CSL-M spec's declaration that number variables in a regular `<text variable>`
/// "should fail validation", that is perfectly valid, because "number variables are a subset of the
/// standard variables":
/// [Spec](https://docs.citationstyles.org/en/stable/specification.html#number-variables)

#[derive(Debug, Eq, Copy, Clone, PartialEq)]
pub enum StandardVariable {
    Ordinary(Variable),
    Number(NumberVariable),
}

impl GetAttribute for StandardVariable {
    fn get_attr(s: &str, features: &Features) -> Result<Self, UnknownAttributeValue> {
        use self::StandardVariable::*;
        if let Ok(v) = Variable::get_attr(s, features) {
            return Ok(Ordinary(v));
        } else if let Ok(v) = NumberVariable::get_attr(s, features) {
            return Ok(Number(v));
        }
        Err(UnknownAttributeValue::new(s))
    }
}

#[derive(AsRefStr, EnumProperty, EnumString, Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[strum(serialize_all = "kebab_case")]
pub enum Variable {
    /// abstract of the item (e.g. the abstract of a journal article)
    Abstract,
    /// reader’s notes about the item content
    Annote,
    /// archive storing the item
    Archive,
    /// storage location within an archive (e.g. a box and folder number)
    /// technically the spec says use an underscore, but that's probably a typo.
    #[strum(serialize = "archive_location", serialize = "archive-location")]
    ArchiveLocation,
    /// geographic location of the archive,
    ArchivePlace,
    /// issuing or judicial authority (e.g. “USPTO” for a patent, “Fairfax Circuit Court” for a legal case)
    /// CSL-M only
    #[strum(props(csl = "1", cslM = "0"))]
    Authority,
    /// active={true} call number (to locate the item in a library)
    CallNumber,
    /// 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.
    CitationLabel,
    /// title of the collection holding the item (e.g. the series title for a book)
    CollectionTitle,
    /// title of the container holding the item (e.g. the book title for a book chapter, the journal title for a journal article)
    ContainerTitle,
    /// short/abbreviated form of “container-title” (also accessible through the “short” form of the “container-title” variable)
    ContainerTitleShort,
    /// physical (e.g. size) or temporal (e.g. running time) dimensions of the item
    Dimensions,
    /// Digital Object Identifier (e.g. “10.1128/AEM.02591-07”)
    #[strum(serialize = "DOI")]
    DOI,
    /// name of the related event (e.g. the conference name when citing a conference paper)
    Event,
    /// geographic location of the related event (e.g. “Amsterdam, the Netherlands”)
    EventPlace,

    /// class, type or genre of the item (e.g. “adventure” for an adventure movie, “PhD dissertation” for a PhD thesis)
    Genre,
    /// International Standard Book Number
    #[strum(serialize = "ISBN")]
    ISBN,
    /// International Standard Serial Number
    #[strum(serialize = "ISSN")]
    ISSN,
    /// geographic scope of relevance (e.g. “US” for a US patent)
    Jurisdiction,
    /// keyword(s) or tag(s) attached to the item
    Keyword,
    /// medium description (e.g. “CD”, “DVD”, etc.)
    Medium,
    /// (short) inline note giving additional item details (e.g. a concise summary or commentary)
    Note,
    /// original publisher, for items that have been republished by a different publisher
    OriginalPublisher,
    /// geographic location of the original publisher (e.g. “London, UK”)
    OriginalPublisherPlace,
    /// title of the original version (e.g. “Война и мир”, the untranslated Russian title of “War and Peace”)
    OriginalTitle,
    /// PubMed Central reference number
    #[strum(serialize = "PMCID")]
    PMCID,
    /// PubMed reference number
    #[strum(serialize = "PMID")]
    PMID,
    /// publisher
    Publisher,
    /// geographic location of the publisher
    PublisherPlace,
    /// resources related to the procedural history of a legal case
    References,
    /// title of the item reviewed by the current item
    ReviewedTitle,
    /// scale of e.g. a map
    Scale,
    /// container section holding the item (e.g. “politics” for a newspaper article).
    /// TODO: CSL-M appears to interpret this as a number variable?
    Section,
    /// from whence the item originates (e.g. a library catalog or database)
    Source,
    /// (publication) status of the item (e.g. “forthcoming”)
    Status,
    /// primary title of the item
    Title,
    /// short/abbreviated form of “title” (also accessible through the “short” form of the “title” variable)
    TitleShort,
    ///  URL (e.g. “https://aem.asm.org/cgi/content/full/74/9/2766”)
    #[strum(serialize = "URL")]
    URL,
    /// version of the item (e.g. “2.0.9” for a software program)
    Version,
    /// disambiguating year suffix in author-date styles (e.g. “a” in “Doe, 1999a”)
    YearSuffix,

    /// CSL-M only
    // Intercept Hereinafter at CiteContext, as it isn't known at Reference-time.
    // Global-per-document config should be its own thing separate from references.
    // TODO: delete any noRef="true" and replace with serde directives not to read from
    // CSL-JSON.
    #[strum(props(csl = "0", cslM = "1", noRef = "true"))]
    Hereinafter,
    /// CSL-M only
    #[strum(props(csl = "0", cslM = "1"))]
    Dummy,
    /// CSL-M only
    #[strum(props(csl = "0", cslM = "1"))]
    LocatorExtra,
    /// CSL-M only
    #[strum(props(csl = "0", cslM = "1"))]
    VolumeTitle,

    /// CSL-M only
    ///
    /// Not documented in the CSL-M spec.
    #[strum(props(csl = "0", cslM = "1"))]
    Committee,

    /// CSL-M only
    ///
    /// Not documented in the CSL-M spec. See [Indigo Book][ib] section 'R26. Short Form
    /// Citation for Court Documents' for its intended use case, and the Juris-M [US cheat
    /// sheet][uscs]
    ///
    /// [uscs]: https://juris-m.github.io/cheat-sheets/us.pdf
    ///
    /// [ib]: https://law.resource.org/pub/us/code/blue/IndigoBook.html
    #[strum(props(csl = "0", cslM = "1"))]
    DocumentName,

    /// CSL-M only
    ///
    /// Not documented in the CSL-M spec.
    ///
    /// TODO: I think variable="gazette-flag" may have been superseded by type="gazette",
    /// but clearly you can still tick the "Gazette Ref" checkbox in Juris-M on a statute.
    /// Ask Frank. See also https://juris-m.github.io/cheat-sheets/us.pdf
    #[strum(props(csl = "0", cslM = "1"))]
    GazetteFlag,

    // TODO: should not be accessible in condition blocks
    Language,
}

impl Variable {
    pub fn should_replace_hyphens(self) -> bool {
        false
    }
    pub fn hyperlink(self, value: &str) -> Option<&str> {
        match self {
            Variable::URL => Some(value),
            Variable::DOI => Some(value),
            _ => None,
        }
    }
}

#[derive(AsRefStr, EnumProperty, EnumString, Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[strum(serialize_all = "kebab_case")]
pub enum NumberVariable {
    ChapterNumber,
    CollectionNumber,
    Edition,
    Issue,
    Number,
    NumberOfPages,
    NumberOfVolumes,
    Volume,

    /// Locator, Page and PageFirst, FRRN, and CiteNumber: These are technically meant to be standard variables in CSL 1.0.1, but the spec
    /// requires us to treat them as numerics for `<label plural="contextual">` anyway.
    ///
    /// 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.
    Locator,

    /// range of pages the item (e.g. a journal article) covers in a container (e.g. a journal issue)
    Page,
    /// first page of the range of pages the item (e.g. a journal article) covers in a container (e.g. a journal issue)
    PageFirst,

    /// 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.
    FirstReferenceNoteNumber,

    /// index (starting at 1) of the cited reference in the bibliography (generated by the CSL processor)
    CitationNumber,

    /// CSL-M only
    #[strum(props(csl = "0", cslM = "1"))]
    PublicationNumber,

    /// CSL-M only
    #[strum(props(csl = "0", cslM = "1"))]
    Supplement,

    /// CSL-M only
    #[strum(props(csl = "0", cslM = "1"))]
    Authority,
}

impl NumberVariable {
    pub fn should_replace_hyphens(self) -> bool {
        self == NumberVariable::Locator
    }
}

#[derive(AsRefStr, EnumProperty, EnumString, Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[strum(serialize_all = "kebab_case")]
pub enum NameVariable {
    /// author
    Author,
    /// editor of the collection holding the item (e.g. the series editor for a book)
    CollectionEditor,
    /// composer (e.g. of a musical score)
    Composer,
    /// author of the container holding the item (e.g. the book author for a book chapter)
    ContainerAuthor,
    /// director (e.g. of a film)
    Director,
    /// editor
    Editor,
    /// managing editor (“Directeur de la Publication” in French)
    EditorialDirector,
    /// illustrator (e.g. of a children’s book)
    Illustrator,
    /// interviewer (e.g. of an interview)
    Interviewer,
    /// ?
    OriginalAuthor,
    /// recipient (e.g. of a letter)
    Recipient,
    /// author of the item reviewed by the current item
    ReviewedAuthor,
    /// translator
    Translator,

    /// CSL-M only
    #[strum(props(csl = "0", cslM = "1"))]
    Authority,

    /// CSL-M only
    ///
    /// The dummy name variable is always empty. Use it to force all name variables called through
    /// a cs:names node to render through cs:substitute, and so suppress whichever is chosen for
    /// rendering to be suppressed through the remainder of the current cite.
    #[strum(props(csl = "0", cslM = "1"))]
    Dummy,
}

#[derive(AsRefStr, EnumProperty, EnumString, Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[strum(serialize_all = "kebab_case")]
pub enum DateVariable {
    /// date the item has been accessed
    Accessed,
    /// ?
    Container,
    /// date the related event took place
    EventDate,
    /// date the item was issued/published
    Issued,
    /// (issue) date of the original version
    OriginalDate,
    /// date the item (e.g. a manuscript) has been submitted for publication
    Submitted,
    /// CSL-M only
    #[strum(props(csl = "0", cslM = "1"))]
    LocatorDate,
    /// CSL-M only
    #[strum(props(csl = "0", cslM = "1"))]
    PublicationDate,
    /// CSL-M only
    #[strum(props(csl = "0", cslM = "1"))]
    AvailableDate,
}