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