citationberg/
lib.rs

1/*!
2A library for parsing CSL styles.
3
4Citationberg deserializes CSL styles from XML into Rust structs. It supports
5[CSL 1.0.2](https://docs.citationstyles.org/en/stable/specification.html).
6
7This crate is not a CSL processor, so you are free to choose whatever data
8model and data types you need for your bibliographic needs. If you need to
9render citations, you can use
10[Hayagriva](https://github.com/typst/hayagriva) which uses this crate under
11the hood.
12
13Parse your style like this:
14
15```rust
16# fn main() -> Result<(), Box<dyn std::error::Error>> {
17use std::fs;
18use citationberg::Style;
19
20let string = fs::read_to_string("tests/independent/ieee.csl")?;
21let style = citationberg::Style::from_xml(&string)?;
22
23let Style::Independent(independent) = style else {
24    panic!("IEEE is an independent style");
25};
26
27assert_eq!(independent.info.title.value, "IEEE");
28# Ok(())
29# }
30```
31
32You can also parse a [`DependentStyle`] or a [`IndependentStyle`] directly.
33*/
34
35#![deny(missing_docs)]
36#![deny(unsafe_code)]
37
38#[cfg(feature = "json")]
39pub mod json;
40pub mod taxonomy;
41
42mod util;
43
44use std::fmt::{self, Debug};
45use std::iter::repeat;
46use std::num::{NonZeroI16, NonZeroUsize};
47
48use quick_xml::de::{Deserializer, SliceReader};
49use serde::{Deserialize, Serialize};
50use taxonomy::{
51    DateVariable, Kind, Locator, NameVariable, NumberOrPageVariable, NumberVariable,
52    OtherTerm, Term, Variable,
53};
54
55use self::util::*;
56
57/// Result type for functions that serialize and deserialize XML.
58pub type XmlResult<T> = Result<T, XmlError>;
59
60/// Error type for functions that serialize and deserialize XML.
61pub type XmlError = quick_xml::de::DeError;
62
63const EVENT_BUFFER_SIZE: Option<NonZeroUsize> = NonZeroUsize::new(4096);
64
65/// Allow every struct with formatting properties to convert to a `Formatting`.
66pub trait ToFormatting {
67    /// Obtain a `Formatting`.
68    fn to_formatting(&self) -> Formatting;
69}
70
71macro_rules! to_formatting {
72    ($name:ty, self) => {
73        impl ToFormatting for $name {
74            fn to_formatting(&self) -> Formatting {
75                Formatting {
76                    font_style: self.font_style,
77                    font_variant: self.font_variant,
78                    font_weight: self.font_weight,
79                    text_decoration: self.text_decoration,
80                    vertical_align: self.vertical_align,
81                }
82            }
83        }
84    };
85    ($name:ty) => {
86        impl ToFormatting for $name {
87            fn to_formatting(&self) -> Formatting {
88                self.formatting.clone()
89            }
90        }
91    };
92}
93
94/// Allow every struct with affix properties to convert to a `Affixes`.
95pub trait ToAffixes {
96    /// Obtain the `Affixes`.
97    fn to_affixes(&self) -> Affixes;
98}
99
100macro_rules! to_affixes {
101    ($name:ty, self) => {
102        impl ToAffixes for $name {
103            fn to_affixes(&self) -> Affixes {
104                Affixes {
105                    prefix: self.prefix.clone(),
106                    suffix: self.suffix.clone(),
107                }
108            }
109        }
110    };
111    ($name:ty) => {
112        impl ToAffixes for $name {
113            fn to_affixes(&self) -> Affixes {
114                self.affixes.clone()
115            }
116        }
117    };
118}
119
120/// A CSL style.
121#[allow(clippy::large_enum_variant)]
122#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
123struct RawStyle {
124    /// The style's metadata.
125    pub info: StyleInfo,
126    /// The locale used if the user didn't specify one.
127    /// Overrides the default locale of the parent style.
128    #[serde(rename = "@default-locale")]
129    #[serde(skip_serializing_if = "Option::is_none")]
130    pub default_locale: Option<LocaleCode>,
131    /// The CSL version the style is compatible with.
132    #[serde(rename = "@version")]
133    pub version: String,
134    /// How notes or in-text citations are displayed. Must be present in
135    /// independent styles.
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub citation: Option<Citation>,
138    /// How bibliographies are displayed.
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub bibliography: Option<Bibliography>,
141    /// The style's settings. Must be present in dependent styles.
142    #[serde(flatten)]
143    pub independent_settings: Option<IndependentStyleSettings>,
144    /// Reusable formatting rules.
145    #[serde(rename = "macro", default)]
146    pub macros: Vec<CslMacro>,
147    /// Override localized strings.
148    #[serde(default)]
149    pub locale: Vec<Locale>,
150}
151
152impl RawStyle {
153    /// Retrieve the link to the parent style for dependent styles.
154    pub fn parent_link(&self) -> Option<&InfoLink> {
155        self.info
156            .link
157            .iter()
158            .find(|link| link.rel == InfoLinkRel::IndependentParent)
159    }
160}
161
162impl From<IndependentStyle> for RawStyle {
163    fn from(value: IndependentStyle) -> Self {
164        Self {
165            info: value.info,
166            default_locale: value.default_locale,
167            version: value.version,
168            citation: Some(value.citation),
169            bibliography: value.bibliography,
170            independent_settings: Some(value.settings),
171            macros: value.macros,
172            locale: value.locale,
173        }
174    }
175}
176
177impl From<DependentStyle> for RawStyle {
178    fn from(value: DependentStyle) -> Self {
179        Self {
180            info: value.info,
181            default_locale: value.default_locale,
182            version: value.version,
183            citation: None,
184            bibliography: None,
185            independent_settings: None,
186            macros: Vec::new(),
187            locale: Vec::new(),
188        }
189    }
190}
191
192impl From<Style> for RawStyle {
193    fn from(value: Style) -> Self {
194        match value {
195            Style::Independent(i) => i.into(),
196            Style::Dependent(d) => d.into(),
197        }
198    }
199}
200
201/// An independent CSL style.
202#[derive(Debug, Clone, Eq, PartialEq, Hash)]
203pub struct IndependentStyle {
204    /// The style's metadata.
205    pub info: StyleInfo,
206    /// The locale used if the user didn't specify one.
207    pub default_locale: Option<LocaleCode>,
208    /// The CSL version the style is compatible with.
209    pub version: String,
210    /// How notes or in-text citations are displayed.
211    pub citation: Citation,
212    /// How bibliographies are displayed.
213    pub bibliography: Option<Bibliography>,
214    /// The style's settings. Must be present in dependent styles.
215    pub settings: IndependentStyleSettings,
216    /// Reusable formatting rules.
217    pub macros: Vec<CslMacro>,
218    /// Override localized strings.
219    pub locale: Vec<Locale>,
220}
221
222impl IndependentStyle {
223    /// Create a style from an XML string.
224    pub fn from_xml(xml: &str) -> XmlResult<Self> {
225        let de = &mut deserializer(xml);
226        IndependentStyle::deserialize(de)
227    }
228
229    /// Remove all non-required data that does not influence the style's
230    /// formatting.
231    pub fn purge(&mut self, level: PurgeLevel) {
232        self.info.purge(level);
233    }
234}
235
236/// How much metadata to remove from the style.
237#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
238pub enum PurgeLevel {
239    /// Retain some basic metadata.
240    Basic,
241    /// Purge all metadata.
242    Full,
243}
244
245impl<'de> Deserialize<'de> for IndependentStyle {
246    fn deserialize<D: serde::Deserializer<'de>>(
247        deserializer: D,
248    ) -> Result<Self, D::Error> {
249        let raw_style = RawStyle::deserialize(deserializer)?;
250        let style: Style = raw_style.try_into().map_err(serde::de::Error::custom)?;
251
252        match style {
253            Style::Independent(i) => Ok(i),
254            Style::Dependent(_) => Err(serde::de::Error::custom(
255                "expected an independent style but got a dependent style",
256            )),
257        }
258    }
259}
260
261/// A dependent CSL style.
262#[derive(Debug, Clone, Eq, PartialEq, Hash)]
263pub struct DependentStyle {
264    /// The style's metadata.
265    pub info: StyleInfo,
266    /// The locale used if the user didn't specify one.
267    /// Overrides the default locale of the parent style.
268    pub default_locale: Option<LocaleCode>,
269    /// The CSL version the style is compatible with.
270    pub version: String,
271    /// The link to the parent style.
272    pub parent_link: InfoLink,
273}
274
275impl DependentStyle {
276    /// Create a style from an XML string.
277    pub fn from_xml(xml: &str) -> XmlResult<Self> {
278        let de = &mut deserializer(xml);
279        DependentStyle::deserialize(de)
280    }
281
282    /// Remove all non-required data that does not influence the style's
283    /// formatting.
284    pub fn purge(&mut self, level: PurgeLevel) {
285        self.info.purge(level);
286    }
287}
288
289impl<'de> Deserialize<'de> for DependentStyle {
290    fn deserialize<D: serde::Deserializer<'de>>(
291        deserializer: D,
292    ) -> Result<Self, D::Error> {
293        let raw_style = RawStyle::deserialize(deserializer)?;
294        let style: Style = raw_style.try_into().map_err(serde::de::Error::custom)?;
295
296        match style {
297            Style::Dependent(d) => Ok(d),
298            Style::Independent(_) => Err(serde::de::Error::custom(
299                "expected a dependent style but got an independent style",
300            )),
301        }
302    }
303}
304
305/// A CSL style.
306#[derive(Debug, Clone, Eq, PartialEq, Hash)]
307#[allow(clippy::large_enum_variant)]
308pub enum Style {
309    /// An independent style.
310    Independent(IndependentStyle),
311    /// A dependent style.
312    Dependent(DependentStyle),
313}
314
315impl Style {
316    /// Create a style from an XML string.
317    pub fn from_xml(xml: &str) -> XmlResult<Self> {
318        let de = &mut deserializer(xml);
319        Style::deserialize(de)
320    }
321
322    /// Write the style to an XML string.
323    pub fn to_xml(&self) -> XmlResult<String> {
324        let mut buf = String::new();
325        let ser = quick_xml::se::Serializer::with_root(&mut buf, Some("style"))?;
326        self.serialize(ser)?;
327        Ok(buf)
328    }
329
330    /// Remove all non-required data that does not influence the style's
331    /// formatting.
332    pub fn purge(&mut self, level: PurgeLevel) {
333        match self {
334            Self::Independent(i) => i.purge(level),
335            Self::Dependent(d) => d.purge(level),
336        }
337    }
338
339    /// Get the style's metadata.
340    pub fn info(&self) -> &StyleInfo {
341        match self {
342            Self::Independent(i) => &i.info,
343            Self::Dependent(d) => &d.info,
344        }
345    }
346}
347
348impl<'de> Deserialize<'de> for Style {
349    fn deserialize<D: serde::Deserializer<'de>>(
350        deserializer: D,
351    ) -> Result<Self, D::Error> {
352        let raw_style = RawStyle::deserialize(deserializer)?;
353        raw_style.try_into().map_err(serde::de::Error::custom)
354    }
355}
356
357impl Serialize for Style {
358    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
359    where
360        S: serde::Serializer,
361    {
362        RawStyle::from(self.clone()).serialize(serializer)
363    }
364}
365
366impl TryFrom<RawStyle> for Style {
367    type Error = StyleValidationError;
368
369    fn try_from(value: RawStyle) -> Result<Self, Self::Error> {
370        let has_bibliography = value.bibliography.is_some();
371        if let Some(citation) = value.citation {
372            if let Some(settings) = value.independent_settings {
373                Ok(Self::Independent(IndependentStyle {
374                    info: value.info,
375                    default_locale: value.default_locale,
376                    version: value.version,
377                    citation,
378                    bibliography: value.bibliography,
379                    settings,
380                    macros: value.macros,
381                    locale: value.locale,
382                }))
383            } else {
384                Err(StyleValidationError::MissingClassAttr)
385            }
386        } else if has_bibliography {
387            Err(StyleValidationError::MissingCitation)
388        } else if let Some(parent_link) = value.parent_link().cloned() {
389            Ok(Self::Dependent(DependentStyle {
390                info: value.info,
391                default_locale: value.default_locale,
392                version: value.version,
393                parent_link,
394            }))
395        } else {
396            Err(StyleValidationError::MissingParent)
397        }
398    }
399}
400
401/// An error that occurred while validating a style.
402#[derive(Debug, Clone, Eq, PartialEq, Hash)]
403pub enum StyleValidationError {
404    /// The CSL style did have a `cs:bibliography` child but not a
405    /// `cs:citation`.
406    MissingCitation,
407    /// A dependent style was missing the `independent-parent` link.
408    MissingParent,
409    /// An independent style was missing the `class` attribute on `cs:style`
410    MissingClassAttr,
411}
412
413impl fmt::Display for StyleValidationError {
414    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
415        f.write_str(match self {
416            Self::MissingCitation => "root element is missing `cs:citation` child despite having a `cs:bibliography`",
417            Self::MissingParent => "`cs:link` tag with `independent-parent` as a `rel` attribute is missing but no `cs:citation` was defined",
418            Self::MissingClassAttr => "`cs:style` tag is missing the `class` attribute",
419        })
420    }
421}
422
423fn deserializer(xml: &str) -> Deserializer<SliceReader<'_>> {
424    let mut style_deserializer = Deserializer::from_str(xml);
425    style_deserializer.event_buffer_size(EVENT_BUFFER_SIZE);
426    style_deserializer
427}
428
429/// A style with its own formatting rules.
430#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
431pub struct IndependentStyleSettings {
432    /// How the citations are displayed.
433    #[serde(rename = "@class")]
434    pub class: StyleClass,
435    /// Whether to use a hyphen when initializing a name.
436    ///
437    /// Defaults to `true`.
438    #[serde(
439        rename = "@initialize-with-hyphen",
440        default = "IndependentStyleSettings::default_initialize_with_hyphen",
441        deserialize_with = "deserialize_bool"
442    )]
443    pub initialize_with_hyphen: bool,
444    /// Specifies how to reformat page ranges.
445    #[serde(rename = "@page-range-format")]
446    #[serde(skip_serializing_if = "Option::is_none")]
447    pub page_range_format: Option<PageRangeFormat>,
448    /// How to treat the non-dropping name particle when printing names.
449    #[serde(rename = "@demote-non-dropping-particle", default)]
450    pub demote_non_dropping_particle: DemoteNonDroppingParticle,
451    /// Options for the names within. Only defined for dependent styles.
452    #[serde(flatten)]
453    pub options: InheritableNameOptions,
454}
455
456impl IndependentStyleSettings {
457    /// Return the default value for `initialize_with_hyphen`.
458    pub const fn default_initialize_with_hyphen() -> bool {
459        true
460    }
461}
462
463/// An RFC 1766 language code.
464#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
465pub struct LocaleCode(pub String);
466
467impl<'a> LocaleCode {
468    /// Get the US English locale.
469    pub fn en_us() -> Self {
470        Self("en-US".to_string())
471    }
472
473    /// Get the base language code.
474    pub fn parse_base(&self) -> Option<BaseLanguage> {
475        let mut parts = self.0.split('-').take(2);
476        let first = parts.next()?;
477
478        match first {
479            "i" | "I" => {
480                let second = parts.next()?;
481                if second.is_empty() {
482                    return None;
483                }
484
485                Some(BaseLanguage::Iana(second.to_string()))
486            }
487            "x" | "X" => {
488                let second = parts.next()?;
489                if second.len() > 8 || second.is_empty() {
490                    return None;
491                }
492
493                let mut code = [0; 8];
494                code[..second.len()].copy_from_slice(second.as_bytes());
495                Some(BaseLanguage::Unregistered(code))
496            }
497            _ if first.len() == 2 => {
498                let mut code = [0; 2];
499                code.copy_from_slice(first.as_bytes());
500                Some(BaseLanguage::Iso639_1(code))
501            }
502            _ => None,
503        }
504    }
505
506    /// Get the language's extensions.
507    pub fn extensions(&'a self) -> impl Iterator<Item = &'a str> + 'a {
508        self.0
509            .split('-')
510            .enumerate()
511            .filter_map(|(i, e)| {
512                if i == 0 && ["x", "X", "i", "I"].contains(&e) {
513                    None
514                } else {
515                    Some(e)
516                }
517            })
518            .skip(1)
519    }
520
521    /// Check whether the language is English.
522    pub fn is_english(&self) -> bool {
523        let en = "en";
524        let hyphen = "-";
525        self.0.starts_with(en)
526            && (self.0.len() == 2
527                || self.0.get(en.len()..en.len() + hyphen.len()) == Some(hyphen))
528    }
529
530    /// Get the fallback locale for a locale.
531    pub fn fallback(&self) -> Option<LocaleCode> {
532        match self.parse_base()? {
533            BaseLanguage::Iso639_1(code) => match &code {
534                b"af" => Some("af-ZA"),
535                b"bg" => Some("bg-BG"),
536                b"ca" => Some("ca-AD"),
537                b"cs" => Some("cs-CZ"),
538                b"da" => Some("da-DK"),
539                b"de" => Some("de-DE"),
540                b"el" => Some("el-GR"),
541                b"en" => Some("en-US"),
542                b"es" => Some("es-ES"),
543                b"et" => Some("et-EE"),
544                b"fa" => Some("fa-IR"),
545                b"fi" => Some("fi-FI"),
546                b"fr" => Some("fr-FR"),
547                b"he" => Some("he-IL"),
548                b"hr" => Some("hr-HR"),
549                b"hu" => Some("hu-HU"),
550                b"is" => Some("is-IS"),
551                b"it" => Some("it-IT"),
552                b"ja" => Some("ja-JP"),
553                b"km" => Some("km-KH"),
554                b"ko" => Some("ko-KR"),
555                b"lt" => Some("lt-LT"),
556                b"lv" => Some("lv-LV"),
557                b"mn" => Some("mn-MN"),
558                b"nb" => Some("nb-NO"),
559                b"nl" => Some("nl-NL"),
560                b"nn" => Some("nn-NO"),
561                b"pl" => Some("pl-PL"),
562                b"pt" => Some("pt-PT"),
563                b"ro" => Some("ro-RO"),
564                b"ru" => Some("ru-RU"),
565                b"sk" => Some("sk-SK"),
566                b"sl" => Some("sl-SI"),
567                b"sr" => Some("sr-RS"),
568                b"sv" => Some("sv-SE"),
569                b"th" => Some("th-TH"),
570                b"tr" => Some("tr-TR"),
571                b"uk" => Some("uk-UA"),
572                b"vi" => Some("vi-VN"),
573                b"zh" => Some("zh-CN"),
574                _ => None,
575            }
576            .map(ToString::to_string)
577            .map(LocaleCode)
578            .filter(|f| f != self),
579            _ => None,
580        }
581    }
582}
583
584impl fmt::Display for LocaleCode {
585    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
586        fmt::Display::fmt(&self.0, f)
587    }
588}
589
590/// The base language in a [`LocaleCode`].
591pub enum BaseLanguage {
592    /// A language code.
593    Iso639_1([u8; 2]),
594    /// An IANA language code.
595    Iana(String),
596    /// An unregistered / experimental language code.
597    Unregistered([u8; 8]),
598}
599
600impl BaseLanguage {
601    /// Get the language code.
602    pub fn as_str(&self) -> &str {
603        match self {
604            Self::Iso639_1(code) => std::str::from_utf8(code).unwrap(),
605            Self::Iana(code) => code,
606            Self::Unregistered(code) => std::str::from_utf8(code).unwrap(),
607        }
608    }
609}
610
611/// How the citations are displayed.
612#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
613#[serde(rename_all = "kebab-case")]
614pub enum StyleClass {
615    /// Citations are inlined in the text.
616    InText,
617    /// Citations are displayed in foot- or endnotes.
618    Note,
619}
620
621/// How to reformat page ranges.
622#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash, Deserialize, Serialize)]
623#[serde(rename_all = "kebab-case")]
624pub enum PageRangeFormat {
625    /// ā€œ321–28ā€
626    /// Aliases: `chicago` until CSL 1.1
627    // Rename needed because the number is not used as word boundary by heck.
628    #[serde(alias = "chicago")]
629    #[serde(rename = "chicago-15")]
630    Chicago15,
631    /// ā€œ321–28ā€
632    #[serde(rename = "chicago-16")]
633    Chicago16,
634    /// ā€œ321–328ā€
635    #[default]
636    Expanded,
637    /// ā€œ321–8ā€
638    Minimal,
639    /// ā€œ321–28ā€
640    MinimalTwo,
641}
642
643impl PageRangeFormat {
644    /// Use a page range format to format a range of pages.
645    ///
646    /// Closely follows the [Haskell implementation of pandoc](https://hackage.haskell.org/package/citeproc-0.8.1/docs/src/Citeproc.Eval.html#pageRange).
647    pub fn format(
648        self,
649        buf: &mut impl fmt::Write,
650        start: &str,
651        end: &str,
652        separator: Option<&str>,
653    ) -> Result<(), fmt::Error> {
654        let separator = separator.unwrap_or("–");
655        let start = start.trim();
656        let end = end.trim();
657
658        // Split into the maximal suffix that is all digits (`x`|`y`),
659        // and the prefix.
660        let (start_pre, x) = split_max_digit_suffix(start);
661        let (end_pre, y) = split_max_digit_suffix(end);
662
663        if start_pre == end_pre {
664            let pref = start_pre;
665            let x_len = x.len();
666            let y_len = y.len();
667            // If `y` is shorter, it is a shorthand notation, e.g., `101-7`.
668            let y = if x_len <= y_len {
669                y.to_string()
670            } else {
671                // Expand `y` to include the missing starting digits from `x`.
672                let mut s = x[..(x_len - y_len)].to_string();
673                s.push_str(y);
674                s
675            };
676
677            // Write what stays the same early
678            write!(buf, "{pref}{x}{separator}")?;
679
680            // https://docs.citationstyles.org/en/stable/specification.html#appendix-v-page-range-formats
681            match self {
682                PageRangeFormat::Chicago15 | PageRangeFormat::Chicago16
683                    if x_len < 3 || x.ends_with("00") =>
684                {
685                    // For `x` < 100 or multiples of 100, write all digits.
686                    write!(buf, "{y}")
687                }
688                PageRangeFormat::Chicago15 | PageRangeFormat::Chicago16
689                    if x[x_len - 2..].starts_with('0') =>
690                {
691                    // For 1 < `x` % 100 < 10, use changed part only.
692                    minimal(buf, 1, x, &y)
693                }
694                PageRangeFormat::Chicago15
695                    if x_len == 4 && changed_digits(x, &y) >= 3 =>
696                {
697                    // If `x` has 4 digits and 3 change, write all digits.
698                    write!(buf, "{y}")
699                }
700                PageRangeFormat::Chicago15 | PageRangeFormat::Chicago16 => {
701                    // Otherwise (for Chicago), write at least 2 digits.
702                    minimal(buf, 2, x, &y)
703                }
704                PageRangeFormat::Expanded => write!(buf, "{pref}{y}"),
705                PageRangeFormat::Minimal => minimal(buf, 1, x, &y),
706                PageRangeFormat::MinimalTwo => minimal(buf, 2, x, &y),
707            }
708        } else {
709            // Prefix is different, write entire range.
710            write!(buf, "{start}{separator}{end}")
711        }
712    }
713}
714
715/// Calculates how many digits are different between `x` and `y`, starting from the back.
716///
717/// Returns as soon as two digits differ. (In that part we differ from the Haskell version. I think this makes more sense.)
718fn changed_digits(x: &str, y: &str) -> usize {
719    let x = if x.len() < y.len() {
720        let mut s = String::from_iter(repeat(' ').take(y.len() - x.len()));
721        s.push_str(x);
722        s
723    } else {
724        x.to_string()
725    };
726    debug_assert!(x.len() == y.len());
727    let xs = x.chars().rev();
728    let ys = y.chars().rev();
729
730    for (i, (c, d)) in xs.zip(ys).enumerate() {
731        if c == d {
732            return i;
733        }
734    }
735
736    x.len()
737}
738
739/// Writes the minimal digits that have changed from `x` to `y`---but at minimum `thresh` digits---to `buf`.
740fn minimal(
741    buf: &mut impl fmt::Write,
742    thresh: usize,
743    x: &str,
744    y: &str,
745) -> Result<(), fmt::Error> {
746    if y.len() > x.len() {
747        // y is no abbrev. write it
748        return write!(buf, "{y}");
749    }
750
751    let mut xs = String::new();
752    let mut ys = String::new();
753    for (c, d) in x.chars().zip(y.chars()).skip_while(|(c, d)| c == d) {
754        xs.push(c);
755        ys.push(d);
756    }
757
758    if ys.len() < thresh && y.len() >= thresh {
759        write!(buf, "{}", &y[(y.len() - thresh)..])
760    } else {
761        write!(buf, "{ys}")
762    }
763}
764
765/// Split `s` into the maximal suffix that is only digits and a prefix.
766fn split_max_digit_suffix(s: &str) -> (&str, &str) {
767    let suffix_len = s.chars().rev().take_while(|c| c.is_ascii_digit()).count();
768    let idx = s.len() - suffix_len;
769    (&s[..idx], &s[idx..])
770}
771
772/// How to treat the non-dropping name particle when printing names.
773#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
774#[serde(rename_all = "kebab-case")]
775pub enum DemoteNonDroppingParticle {
776    /// Treat as part of the first name.
777    Never,
778    /// Treat as part of the first name except when sorting.
779    SortOnly,
780    /// Treat as part of the family name.
781    #[default]
782    DisplayAndSort,
783}
784
785/// Citation style metadata
786#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
787#[serde(rename_all = "kebab-case")]
788pub struct StyleInfo {
789    /// The authors of the style
790    #[serde(rename = "author")]
791    #[serde(default)]
792    pub authors: Vec<StyleAttribution>,
793    /// Contributors to the style
794    #[serde(rename = "contributor")]
795    #[serde(default)]
796    pub contibutors: Vec<StyleAttribution>,
797    /// Which format the citations are in.
798    #[serde(default)]
799    pub category: Vec<StyleCategory>,
800    /// Which academic field the style is used in.
801    #[serde(default)]
802    pub field: Vec<Field>,
803    /// A unique identifier for the style. May be a URL or an UUID.
804    pub id: String,
805    /// The ISSN for the source of the style's publication.
806    #[serde(default)]
807    pub issn: Vec<String>,
808    /// The eISSN for the source of the style's publication.
809    #[serde(skip_serializing_if = "Option::is_none")]
810    pub eissn: Option<String>,
811    /// The ISSN-L for the source of the style's publication.
812    #[serde(skip_serializing_if = "Option::is_none")]
813    pub issnl: Option<String>,
814    /// Links with more information about the style.
815    #[serde(default)]
816    pub link: Vec<InfoLink>,
817    /// When the style was initially published.
818    #[serde(skip_serializing_if = "Option::is_none")]
819    pub published: Option<Timestamp>,
820    /// Under which license the style is published.
821    #[serde(skip_serializing_if = "Option::is_none")]
822    pub rights: Option<License>,
823    /// A short description of the style.
824    #[serde(skip_serializing_if = "Option::is_none")]
825    pub summary: Option<LocalString>,
826    /// The title of the style.
827    pub title: LocalString,
828    /// A shortened version of the title.
829    #[serde(skip_serializing_if = "Option::is_none")]
830    pub title_short: Option<LocalString>,
831    /// When the style was last updated.
832    #[serde(skip_serializing_if = "Option::is_none")]
833    pub updated: Option<Timestamp>,
834}
835
836impl StyleInfo {
837    /// Remove all non-required fields.
838    pub fn purge(&mut self, level: PurgeLevel) {
839        self.field.clear();
840        self.issn.clear();
841        self.eissn = None;
842        self.issnl = None;
843        self.published = None;
844        self.summary = None;
845        self.updated = None;
846
847        match level {
848            PurgeLevel::Basic => {
849                for person in self.authors.iter_mut().chain(self.contibutors.iter_mut()) {
850                    person.email = None;
851                    person.uri = None;
852                }
853                self.link.retain(|i| {
854                    matches!(i.rel, InfoLinkRel::IndependentParent | InfoLinkRel::Zelf)
855                });
856            }
857            PurgeLevel::Full => {
858                self.authors.clear();
859                self.contibutors.clear();
860                self.link.retain(|i| i.rel == InfoLinkRel::IndependentParent);
861                self.rights = None;
862            }
863        }
864    }
865}
866
867/// A string annotated with a locale.
868#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
869pub struct LocalString {
870    /// The string's locale.
871    #[serde(rename = "@lang")]
872    #[serde(skip_serializing_if = "Option::is_none")]
873    pub lang: Option<LocaleCode>,
874    /// The string's value.
875    #[serde(rename = "$value", default)]
876    pub value: String,
877}
878
879/// A person affiliated with the style.
880#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
881pub struct StyleAttribution {
882    /// The person's name.
883    pub name: String,
884    /// The person's email address.
885    #[serde(skip_serializing_if = "Option::is_none")]
886    pub email: Option<String>,
887    /// A URI for the person.
888    #[serde(skip_serializing_if = "Option::is_none")]
889    pub uri: Option<String>,
890}
891
892/// Which category this style belongs in.
893#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
894#[serde(untagged)]
895pub enum StyleCategory {
896    /// Which format the citations are in. May only appear once as a child of `category`.
897    CitationFormat {
898        /// Which format the citations are in.
899        #[serde(rename = "@citation-format")]
900        format: CitationFormat,
901    },
902    /// Which academic field the style is used in. May appear multiple times as a child of `category`.
903    Field {
904        /// Which academic field the style is used in.
905        #[serde(rename = "@field")]
906        field: Field,
907    },
908}
909
910/// What type of in-text citation is used.
911#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
912#[serde(rename_all = "kebab-case")]
913pub enum CitationFormat {
914    /// ā€œā€¦ (Doe, 1999)ā€
915    AuthorDate,
916    /// ā€œā€¦ (Doe)ā€
917    Author,
918    /// ā€œā€¦ \[1\]ā€
919    Numeric,
920    /// ā€œā€¦ \[doe99\]ā€
921    Label,
922    /// The citation appears as a foot- or endnote.
923    Note,
924}
925
926/// In which academic field the style is used.
927#[allow(missing_docs)]
928#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
929#[serde(rename_all = "snake_case")]
930pub enum Field {
931    Anthropology,
932    Astronomy,
933    Biology,
934    Botany,
935    Chemistry,
936    Communications,
937    Engineering,
938    /// Used for generic styles like Harvard and APA.
939    #[serde(rename = "generic-base")]
940    GenericBase,
941    Geography,
942    Geology,
943    History,
944    Humanities,
945    Law,
946    Linguistics,
947    Literature,
948    Math,
949    Medicine,
950    Philosophy,
951    Physics,
952    PoliticalScience,
953    Psychology,
954    Science,
955    SocialScience,
956    Sociology,
957    Theology,
958    Zoology,
959}
960
961/// A link with more information about the style.
962#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
963pub struct InfoLink {
964    /// The link's URL.
965    #[serde(rename = "@href")]
966    pub href: String,
967    /// How the link relates to the style.
968    #[serde(rename = "@rel")]
969    pub rel: InfoLinkRel,
970    /// A human-readable description of the link.
971    #[serde(rename = "$value")]
972    #[serde(skip_serializing_if = "Option::is_none")]
973    pub description: Option<String>,
974    /// The link's locale.
975    #[serde(rename = "@xml:lang")]
976    #[serde(skip_serializing_if = "Option::is_none")]
977    pub locale: Option<LocaleCode>,
978}
979
980/// How a link relates to the style.
981#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
982#[serde(rename_all = "kebab-case")]
983pub enum InfoLinkRel {
984    /// Website of the style.
985    #[serde(rename = "self")]
986    Zelf,
987    /// URL from which the style is derived. Must not appear in dependent styles.
988    Template,
989    /// URL of the style's documentation.
990    Documentation,
991    /// Parent of a dependent style. Must appear in dependent styles.
992    IndependentParent,
993}
994
995/// An ISO 8601 chapter 5.4 timestamp.
996#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
997pub struct Timestamp {
998    /// The timestamp's value.
999    #[serde(rename = "$text")]
1000    pub raw: String,
1001}
1002
1003/// A license description.
1004#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1005pub struct License {
1006    /// The license's name.
1007    #[serde(rename = "$text")]
1008    pub name: String,
1009    /// The license's URL.
1010    #[serde(rename = "@license")]
1011    #[serde(skip_serializing_if = "Option::is_none")]
1012    pub license: Option<String>,
1013    /// The license string's locale.
1014    #[serde(rename = "@xml:lang")]
1015    #[serde(skip_serializing_if = "Option::is_none")]
1016    pub lang: Option<LocaleCode>,
1017}
1018
1019/// Formatting instructions for in-text or note citations.
1020#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1021#[serde(rename_all = "kebab-case")]
1022pub struct Citation {
1023    /// How items are sorted within the citation.
1024    #[serde(skip_serializing_if = "Option::is_none")]
1025    pub sort: Option<Sort>,
1026    /// The citation's formatting rules.
1027    pub layout: Layout,
1028    /// Expand names that are ambiguous in short form.
1029    ///
1030    /// Default: `false`
1031    #[serde(
1032        rename = "@disambiguate-add-givenname",
1033        default,
1034        deserialize_with = "deserialize_bool"
1035    )]
1036    pub disambiguate_add_givenname: bool,
1037    /// When to expand names that are ambiguous in short form.
1038    #[serde(rename = "@givenname-disambiguation-rule", default)]
1039    pub givenname_disambiguation_rule: DisambiguationRule,
1040    /// Disambiguate by adding more names that would otherwise be hidden by et al.
1041    ///
1042    /// Default: `false`
1043    #[serde(
1044        rename = "@disambiguate-add-names",
1045        default,
1046        deserialize_with = "deserialize_bool"
1047    )]
1048    pub disambiguate_add_names: bool,
1049    /// Disambiguate by adding an alphabetical suffix to the year.
1050    ///
1051    /// Default: `false`
1052    #[serde(
1053        rename = "@disambiguate-add-year-suffix",
1054        default,
1055        deserialize_with = "deserialize_bool"
1056    )]
1057    pub disambiguate_add_year_suffix: bool,
1058    /// Group items in cite by name.
1059    #[serde(rename = "@cite-group-delimiter")]
1060    #[serde(skip_serializing_if = "Option::is_none")]
1061    pub cite_group_delimiter: Option<String>,
1062    /// How to collapse cites with similar items.
1063    #[serde(rename = "@collapse")]
1064    #[serde(skip_serializing_if = "Option::is_none")]
1065    pub collapse: Option<Collapse>,
1066    /// Delimiter between year suffixes.
1067    #[serde(rename = "@year-suffix-delimiter")]
1068    #[serde(skip_serializing_if = "Option::is_none")]
1069    pub year_suffix_delimiter: Option<String>,
1070    /// Delimiter after a collapsed cite group.
1071    #[serde(rename = "@after-collapse-delimiter")]
1072    #[serde(skip_serializing_if = "Option::is_none")]
1073    pub after_collapse_delimiter: Option<String>,
1074    /// When near-note-distance is true.
1075    ///
1076    /// Default: `5`
1077    #[serde(
1078        rename = "@near-note-distance",
1079        default = "Citation::default_near_note_distance",
1080        deserialize_with = "deserialize_u32"
1081    )]
1082    pub near_note_distance: u32,
1083    /// Options for the names within.
1084    #[serde(flatten)]
1085    pub name_options: InheritableNameOptions,
1086}
1087
1088impl Citation {
1089    /// Return the default value for `cite_group_delimiter` if implicitly needed
1090    /// due to presence of a `collapse` attribute.
1091    pub const DEFAULT_CITE_GROUP_DELIMITER: &'static str = ", ";
1092
1093    /// Return a citation with default settings and the given layout.
1094    pub fn with_layout(layout: Layout) -> Self {
1095        Self {
1096            sort: None,
1097            layout,
1098            disambiguate_add_givenname: false,
1099            givenname_disambiguation_rule: DisambiguationRule::default(),
1100            disambiguate_add_names: false,
1101            disambiguate_add_year_suffix: false,
1102            cite_group_delimiter: None,
1103            collapse: None,
1104            year_suffix_delimiter: None,
1105            after_collapse_delimiter: None,
1106            near_note_distance: Self::default_near_note_distance(),
1107            name_options: Default::default(),
1108        }
1109    }
1110
1111    /// Return the `year_suffix_delimiter`.
1112    pub fn get_year_suffix_delimiter(&self) -> &str {
1113        self.year_suffix_delimiter
1114            .as_deref()
1115            .or(self.layout.delimiter.as_deref())
1116            .unwrap_or_default()
1117    }
1118
1119    /// Return the `after_collapse_delimiter`.
1120    pub fn get_after_collapse_delimiter(&self) -> &str {
1121        self.after_collapse_delimiter
1122            .as_deref()
1123            .or(self.layout.delimiter.as_deref())
1124            .unwrap_or_default()
1125    }
1126
1127    /// Return the default `near_note_distance`.
1128    pub const fn default_near_note_distance() -> u32 {
1129        5
1130    }
1131}
1132
1133/// When to expand names that are ambiguous in short form.
1134#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1135#[serde(rename_all = "kebab-case")]
1136pub enum DisambiguationRule {
1137    /// Expand to disambiguate both cites and names.
1138    AllNames,
1139    /// Expand to disambiguate cites and names but only use initials.
1140    AllNamesWithInitials,
1141    /// Same as `AllNames` but only disambiguate the first person in a citation.
1142    PrimaryName,
1143    /// Same as `AllNamesWithInitials` but only disambiguate the first person in a citation.
1144    PrimaryNameWithInitials,
1145    /// Expand to disambiguate cites but not names.
1146    #[default]
1147    ByCite,
1148}
1149
1150impl DisambiguationRule {
1151    /// Whether this rule allows full first names or only initials.
1152    pub fn allows_full_first_names(self) -> bool {
1153        match self {
1154            Self::AllNames | Self::PrimaryName | Self::ByCite => true,
1155            Self::AllNamesWithInitials | Self::PrimaryNameWithInitials => false,
1156        }
1157    }
1158
1159    /// Whether this rule allows looking beyond the first name.
1160    pub fn allows_multiple_names(self) -> bool {
1161        match self {
1162            Self::AllNames | Self::AllNamesWithInitials | Self::ByCite => true,
1163            Self::PrimaryName | Self::PrimaryNameWithInitials => false,
1164        }
1165    }
1166}
1167
1168/// How to collapse cites with similar items.
1169#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1170#[serde(rename_all = "kebab-case")]
1171pub enum Collapse {
1172    /// Collapse items with increasing ranges for numeric styles.
1173    CitationNumber,
1174    /// Collapse items with the same authors and different years by omitting the author.
1175    Year,
1176    /// Same as `Year`, but equal years are omitted as well.
1177    YearSuffix,
1178    /// Same as `YearSuffix`, but also collapse the suffixes into a range.
1179    YearSuffixRanged,
1180}
1181
1182/// Formatting instructions for the bibliography.
1183#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1184pub struct Bibliography {
1185    /// How items are sorted within the citation.
1186    #[serde(skip_serializing_if = "Option::is_none")]
1187    pub sort: Option<Sort>,
1188    /// The citation's formatting rules.
1189    pub layout: Layout,
1190    /// Render the bibliography in a hanging indent.
1191    ///
1192    /// Default: `false`
1193    #[serde(rename = "@hanging-indent", default, deserialize_with = "deserialize_bool")]
1194    pub hanging_indent: bool,
1195    /// When set, the second field is aligned.
1196    #[serde(rename = "@second-field-align")]
1197    #[serde(skip_serializing_if = "Option::is_none")]
1198    pub second_field_align: Option<SecondFieldAlign>,
1199    /// The line spacing within the bibliography as a multiple of regular line spacing.
1200    #[serde(rename = "@line-spacing", default = "Bibliography::default_line_spacing")]
1201    pub line_spacing: NonZeroI16,
1202    /// Extra space between entries as a multiple of line height.
1203    #[serde(rename = "@entry-spacing", default = "Bibliography::default_entry_spacing")]
1204    pub entry_spacing: i16,
1205    /// When set, subsequent identical names are replaced with this.
1206    #[serde(rename = "@subsequent-author-substitute")]
1207    #[serde(skip_serializing_if = "Option::is_none")]
1208    pub subsequent_author_substitute: Option<String>,
1209    /// How to replace subsequent identical names.
1210    #[serde(rename = "@subsequent-author-substitute-rule", default)]
1211    pub subsequent_author_substitute_rule: SubsequentAuthorSubstituteRule,
1212    /// Options for the names within.
1213    #[serde(flatten)]
1214    pub name_options: InheritableNameOptions,
1215}
1216
1217impl Bibliography {
1218    /// Return a bibliography with default settings and the given layout.
1219    pub fn with_layout(layout: Layout) -> Self {
1220        Self {
1221            sort: None,
1222            layout,
1223            hanging_indent: false,
1224            second_field_align: None,
1225            line_spacing: Self::default_line_spacing(),
1226            entry_spacing: Self::default_entry_spacing(),
1227            subsequent_author_substitute: None,
1228            subsequent_author_substitute_rule: Default::default(),
1229            name_options: Default::default(),
1230        }
1231    }
1232
1233    /// Return the default `line_spacing`.
1234    fn default_line_spacing() -> NonZeroI16 {
1235        NonZeroI16::new(1).unwrap()
1236    }
1237
1238    /// Return the default `entry_spacing`.
1239    const fn default_entry_spacing() -> i16 {
1240        1
1241    }
1242}
1243
1244/// How to position the first field if the second field is aligned in a bibliography.
1245#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1246#[serde(rename_all = "kebab-case")]
1247pub enum SecondFieldAlign {
1248    /// Put the first field in the margin and align with the margin.
1249    Margin,
1250    /// Flush the first field with the margin.
1251    Flush,
1252}
1253
1254/// How to replace subsequent identical names in a bibliography.
1255#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1256#[serde(rename_all = "kebab-case")]
1257pub enum SubsequentAuthorSubstituteRule {
1258    /// When all names match, replace.
1259    #[default]
1260    CompleteAll,
1261    /// When all names match, replace each name.
1262    CompleteEach,
1263    /// Each matching name is replaced.
1264    PartialEach,
1265    /// Only the first matching name is replaced.
1266    PartialFirst,
1267}
1268
1269/// How to sort elements in a bibliography or citation.
1270#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1271pub struct Sort {
1272    /// The ordered list of sorting keys.
1273    #[serde(rename = "key")]
1274    pub keys: Vec<SortKey>,
1275}
1276
1277/// A sorting key.
1278#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1279#[serde(untagged)]
1280pub enum SortKey {
1281    /// Sort by the value of a variable.
1282    Variable {
1283        /// The variable to sort by.
1284        #[serde(rename = "@variable")]
1285        variable: Variable,
1286        /// In which direction to sort.
1287        #[serde(rename = "@sort", default)]
1288        sort_direction: SortDirection,
1289    },
1290    /// Sort by the output of a macro.
1291    MacroName {
1292        /// The name of the macro.
1293        #[serde(rename = "@macro")]
1294        name: String,
1295        /// Override `[InheritedNameOptions::et_al_min]` and
1296        /// `[InheritedNameOptions::et_al_subsequent_min]` for macros.
1297        #[serde(
1298            rename = "@names-min",
1299            deserialize_with = "deserialize_u32_option",
1300            default
1301        )]
1302        #[serde(skip_serializing_if = "Option::is_none")]
1303        names_min: Option<u32>,
1304        /// Override `[InheritedNameOptions::et_al_use_first]` and
1305        /// `[InheritedNameOptions::et_al_subsequent_use_first]` for macros.
1306        #[serde(
1307            rename = "@names-use-first",
1308            deserialize_with = "deserialize_u32_option",
1309            default
1310        )]
1311        #[serde(skip_serializing_if = "Option::is_none")]
1312        names_use_first: Option<u32>,
1313        /// Override `[InheritedNameOptions::et_al_use_last]` for macros.
1314        #[serde(
1315            rename = "@names-use-last",
1316            deserialize_with = "deserialize_bool_option",
1317            default
1318        )]
1319        #[serde(skip_serializing_if = "Option::is_none")]
1320        names_use_last: Option<bool>,
1321        /// In which direction to sort.
1322        #[serde(rename = "@sort", default)]
1323        sort_direction: SortDirection,
1324    },
1325}
1326
1327impl From<Variable> for SortKey {
1328    fn from(value: Variable) -> Self {
1329        Self::Variable {
1330            variable: value,
1331            sort_direction: SortDirection::default(),
1332        }
1333    }
1334}
1335
1336impl SortKey {
1337    /// Retrieve the sort direction.
1338    pub const fn sort_direction(&self) -> SortDirection {
1339        match self {
1340            Self::Variable { sort_direction, .. } => *sort_direction,
1341            Self::MacroName { sort_direction, .. } => *sort_direction,
1342        }
1343    }
1344}
1345
1346/// The direction to sort in.
1347#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1348#[serde(rename_all = "kebab-case")]
1349pub enum SortDirection {
1350    /// Sort in ascending order.
1351    #[default]
1352    Ascending,
1353    /// Sort in descending order.
1354    Descending,
1355}
1356
1357/// A formatting rule.
1358#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1359pub struct Layout {
1360    /// Parts of the rule.
1361    #[serde(rename = "$value")]
1362    pub elements: Vec<LayoutRenderingElement>,
1363    // Formatting and affixes fields are rolled into this because
1364    // #[serde(flatten)] doesn't work with $value fields.
1365    /// Set the font style.
1366    #[serde(rename = "@font-style")]
1367    #[serde(skip_serializing_if = "Option::is_none")]
1368    pub font_style: Option<FontStyle>,
1369    /// Choose normal or small caps.
1370    #[serde(rename = "@font-variant")]
1371    #[serde(skip_serializing_if = "Option::is_none")]
1372    pub font_variant: Option<FontVariant>,
1373    /// Set the font weight.
1374    #[serde(rename = "@font-weight")]
1375    #[serde(skip_serializing_if = "Option::is_none")]
1376    pub font_weight: Option<FontWeight>,
1377    /// Choose underlining.
1378    #[serde(rename = "@text-decoration")]
1379    #[serde(skip_serializing_if = "Option::is_none")]
1380    pub text_decoration: Option<TextDecoration>,
1381    /// Choose vertical alignment.
1382    #[serde(rename = "@vertical-align")]
1383    #[serde(skip_serializing_if = "Option::is_none")]
1384    pub vertical_align: Option<VerticalAlign>,
1385    /// The prefix.
1386    #[serde(rename = "@prefix")]
1387    #[serde(skip_serializing_if = "Option::is_none")]
1388    pub prefix: Option<String>,
1389    /// The suffix.
1390    #[serde(rename = "@suffix")]
1391    #[serde(skip_serializing_if = "Option::is_none")]
1392    pub suffix: Option<String>,
1393    /// Delimit pieces of the output.
1394    #[serde(rename = "@delimiter")]
1395    #[serde(skip_serializing_if = "Option::is_none")]
1396    pub delimiter: Option<String>,
1397}
1398
1399to_formatting!(Layout, self);
1400to_affixes!(Layout, self);
1401
1402impl Layout {
1403    /// Return a layout.
1404    pub fn new(
1405        elements: Vec<LayoutRenderingElement>,
1406        formatting: Formatting,
1407        affixes: Option<Affixes>,
1408        delimiter: Option<String>,
1409    ) -> Self {
1410        let (prefix, suffix) = if let Some(affixes) = affixes {
1411            (affixes.prefix, affixes.suffix)
1412        } else {
1413            (None, None)
1414        };
1415
1416        Self {
1417            elements,
1418            font_style: formatting.font_style,
1419            font_variant: formatting.font_variant,
1420            font_weight: formatting.font_weight,
1421            text_decoration: formatting.text_decoration,
1422            vertical_align: formatting.vertical_align,
1423            prefix,
1424            suffix,
1425            delimiter,
1426        }
1427    }
1428
1429    /// Return a layout with default settings and the given elements.
1430    pub fn with_elements(elements: Vec<LayoutRenderingElement>) -> Self {
1431        Self::new(elements, Formatting::default(), None, None)
1432    }
1433
1434    /// Find the child element that will render the given variable.
1435    pub fn find_variable_element(
1436        &self,
1437        variable: Variable,
1438        macros: &[CslMacro],
1439    ) -> Option<LayoutRenderingElement> {
1440        self.elements
1441            .iter()
1442            .find_map(|e| e.find_variable_element(variable, macros))
1443    }
1444}
1445
1446/// Possible parts of a formatting rule.
1447#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1448#[serde(rename_all = "kebab-case")]
1449pub enum LayoutRenderingElement {
1450    /// Insert a term or variable.
1451    Text(Text),
1452    /// Format a date.
1453    Date(Date),
1454    /// Format a number.
1455    Number(Number),
1456    /// Format a list of names.
1457    Names(Names),
1458    /// Prints a label for a variable.
1459    Label(Label),
1460    /// Container for rendering elements.
1461    Group(Group),
1462    /// Conditional rendering.
1463    Choose(Choose),
1464}
1465
1466impl LayoutRenderingElement {
1467    /// Find the child element that will render the given variable.
1468    pub fn find_variable_element(
1469        &self,
1470        variable: Variable,
1471        macros: &[CslMacro],
1472    ) -> Option<Self> {
1473        match self {
1474            Self::Text(t) => t.find_variable_element(variable, macros),
1475            Self::Choose(c) => c.find_variable_element(variable, macros),
1476            Self::Date(d) => {
1477                if d.variable.map(Variable::Date) == Some(variable) {
1478                    Some(self.clone())
1479                } else {
1480                    None
1481                }
1482            }
1483            Self::Number(n) => {
1484                if Variable::Number(n.variable) == variable {
1485                    Some(self.clone())
1486                } else {
1487                    None
1488                }
1489            }
1490            Self::Names(n) => {
1491                if n.variable.iter().any(|v| Variable::Name(*v) == variable) {
1492                    Some(self.clone())
1493                } else {
1494                    None
1495                }
1496            }
1497            Self::Group(g) => g
1498                .children
1499                .iter()
1500                .find_map(|e| e.find_variable_element(variable, macros)),
1501            Self::Label(_) => None,
1502        }
1503    }
1504}
1505
1506/// Rendering elements.
1507#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1508#[serde(untagged)]
1509pub enum RenderingElement {
1510    /// A layout element.
1511    Layout(Layout),
1512    /// Other rendering elements.
1513    Other(LayoutRenderingElement),
1514}
1515
1516/// Print a term or variable.
1517#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1518pub struct Text {
1519    /// The term or variable to print.
1520    #[serde(flatten)]
1521    pub target: TextTarget,
1522    /// Override formatting style.
1523    #[serde(flatten)]
1524    pub formatting: Formatting,
1525    /// Add prefix and suffix.
1526    #[serde(flatten)]
1527    pub affixes: Affixes,
1528    /// Set layout level.
1529    #[serde(rename = "@display")]
1530    #[serde(skip_serializing_if = "Option::is_none")]
1531    pub display: Option<Display>,
1532    /// Whether to wrap this text in quotes.
1533    ///
1534    /// Default: `false`
1535    #[serde(rename = "@quotes", default, deserialize_with = "deserialize_bool")]
1536    pub quotes: bool,
1537    /// Remove periods from the output.
1538    ///
1539    /// Default: `false`
1540    #[serde(rename = "@strip-periods", default, deserialize_with = "deserialize_bool")]
1541    pub strip_periods: bool,
1542    /// Transform the text case.
1543    #[serde(rename = "@text-case")]
1544    #[serde(skip_serializing_if = "Option::is_none")]
1545    pub text_case: Option<TextCase>,
1546}
1547
1548impl Text {
1549    /// Return a text with default settings and the given target.
1550    pub fn with_target(target: impl Into<TextTarget>) -> Self {
1551        Self {
1552            target: target.into(),
1553            formatting: Default::default(),
1554            affixes: Default::default(),
1555            display: None,
1556            quotes: false,
1557            strip_periods: false,
1558            text_case: None,
1559        }
1560    }
1561
1562    /// Find the child element that will render the given variable.
1563    pub fn find_variable_element(
1564        &self,
1565        variable: Variable,
1566        macros: &[CslMacro],
1567    ) -> Option<LayoutRenderingElement> {
1568        match &self.target {
1569            TextTarget::Variable { var, .. } => {
1570                if *var == variable {
1571                    Some(LayoutRenderingElement::Text(self.clone()))
1572                } else {
1573                    None
1574                }
1575            }
1576            TextTarget::Macro { name } => {
1577                if let Some(m) = macros.iter().find(|m| m.name == *name) {
1578                    m.children
1579                        .iter()
1580                        .find_map(|e| e.find_variable_element(variable, macros))
1581                } else {
1582                    None
1583                }
1584            }
1585            TextTarget::Term { .. } => None,
1586            TextTarget::Value { .. } => None,
1587        }
1588    }
1589}
1590
1591to_formatting!(Text);
1592to_affixes!(Text);
1593
1594/// Various kinds of text targets.
1595#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1596#[serde(untagged)]
1597pub enum TextTarget {
1598    /// Prints the value of a variable.
1599    Variable {
1600        #[serde(rename = "@variable")]
1601        /// The variable to print.
1602        var: Variable,
1603        #[serde(rename = "@form", default)]
1604        /// The form of the variable.
1605        form: LongShortForm,
1606    },
1607    /// Prints the text output of a macro.
1608    Macro {
1609        #[serde(rename = "@macro")]
1610        /// The name of the macro.
1611        name: String,
1612    },
1613    /// Prints a localized term.
1614    Term {
1615        /// The term to print.
1616        #[serde(rename = "@term")]
1617        term: Term,
1618        /// The form of the term.
1619        #[serde(rename = "@form", default)]
1620        form: TermForm,
1621        /// Whether the term is pluralized.
1622        #[serde(rename = "@plural", default, deserialize_with = "deserialize_bool")]
1623        plural: bool,
1624    },
1625    /// Prints a given string.
1626    Value {
1627        #[serde(rename = "@value")]
1628        /// The string to print.
1629        val: String,
1630    },
1631}
1632
1633impl From<Variable> for TextTarget {
1634    fn from(value: Variable) -> Self {
1635        Self::Variable { var: value, form: LongShortForm::default() }
1636    }
1637}
1638
1639impl From<Term> for TextTarget {
1640    fn from(value: Term) -> Self {
1641        Self::Term {
1642            term: value,
1643            form: TermForm::default(),
1644            plural: bool::default(),
1645        }
1646    }
1647}
1648
1649/// Formats a date.
1650#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1651#[serde(rename_all = "kebab-case")]
1652pub struct Date {
1653    /// The date to format.
1654    #[serde(rename = "@variable")]
1655    #[serde(skip_serializing_if = "Option::is_none")]
1656    pub variable: Option<DateVariable>,
1657    /// How the localized date should be formatted.
1658    #[serde(rename = "@form")]
1659    #[serde(skip_serializing_if = "Option::is_none")]
1660    pub form: Option<DateForm>,
1661    /// Which parts of the localized date should be included.
1662    #[serde(rename = "@date-parts")]
1663    #[serde(skip_serializing_if = "Option::is_none")]
1664    pub parts: Option<DateParts>,
1665    /// Override the default date parts. Also specifies the order of the parts
1666    /// if `form` is `None`.
1667    #[serde(default)]
1668    pub date_part: Vec<DatePart>,
1669    /// Override formatting style.
1670    #[serde(flatten)]
1671    pub formatting: Formatting,
1672    /// Add prefix and suffix. Ignored when this defines a localized date format.
1673    #[serde(flatten)]
1674    pub affixes: Affixes,
1675    /// Delimit pieces of the output. Ignored when this defines a localized date format.
1676    #[serde(rename = "@delimiter")]
1677    #[serde(skip_serializing_if = "Option::is_none")]
1678    pub delimiter: Option<String>,
1679    /// Set layout level.
1680    #[serde(rename = "@display")]
1681    #[serde(skip_serializing_if = "Option::is_none")]
1682    pub display: Option<Display>,
1683    /// Transform the text case.
1684    #[serde(rename = "@text-case")]
1685    #[serde(skip_serializing_if = "Option::is_none")]
1686    pub text_case: Option<TextCase>,
1687}
1688
1689to_formatting!(Date);
1690to_affixes!(Date);
1691
1692impl Date {
1693    /// Whether this is a localized or a standalone date.
1694    pub const fn is_localized(&self) -> bool {
1695        self.form.is_some()
1696    }
1697}
1698
1699/// Localized date formats.
1700#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1701#[serde(rename_all = "kebab-case")]
1702pub enum DateForm {
1703    /// ā€œ12-15-2005ā€
1704    Numeric,
1705    /// ā€œDecember 15, 2005ā€
1706    Text,
1707}
1708
1709/// Which parts of a date should be included.
1710#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1711#[allow(missing_docs)]
1712#[serde(rename_all = "kebab-case")]
1713pub enum DateParts {
1714    Year,
1715    YearMonth,
1716    #[default]
1717    YearMonthDay,
1718}
1719
1720impl DateParts {
1721    /// Check if the date shall contain a month.
1722    pub const fn has_month(self) -> bool {
1723        matches!(self, Self::YearMonth | Self::YearMonthDay)
1724    }
1725
1726    /// Check if the date shall contain a day.
1727    pub const fn has_day(self) -> bool {
1728        matches!(self, Self::YearMonthDay)
1729    }
1730}
1731
1732/// Override the default date parts.
1733#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1734pub struct DatePart {
1735    /// Kind of the date part.
1736    #[serde(rename = "@name")]
1737    pub name: DatePartName,
1738    /// Form of the date part.
1739    #[serde(rename = "@form")]
1740    #[serde(skip_serializing_if = "Option::is_none")]
1741    form: Option<DateAnyForm>,
1742    /// The string used to delimit two date parts.
1743    #[serde(rename = "@range-delimiter")]
1744    #[serde(skip_serializing_if = "Option::is_none")]
1745    pub range_delimiter: Option<String>,
1746    /// Override formatting style.
1747    #[serde(flatten)]
1748    pub formatting: Formatting,
1749    /// Add prefix and suffix. Ignored when this defines a localized date format.
1750    #[serde(flatten)]
1751    pub affixes: Affixes,
1752    /// Remove periods from the date part.
1753    ///
1754    /// Default: `false`
1755    #[serde(rename = "@strip-periods", default, deserialize_with = "deserialize_bool")]
1756    pub strip_periods: bool,
1757    /// Transform the text case.
1758    #[serde(rename = "@text-case")]
1759    #[serde(skip_serializing_if = "Option::is_none")]
1760    pub text_case: Option<TextCase>,
1761}
1762
1763to_formatting!(DatePart);
1764to_affixes!(DatePart);
1765
1766impl DatePart {
1767    /// Retrieve the default delimiter for the date part.
1768    pub const DEFAULT_DELIMITER: &'static str = "–";
1769
1770    /// Retrieve the form.
1771    pub fn form(&self) -> DateStrongAnyForm {
1772        DateStrongAnyForm::for_name(self.name, self.form)
1773    }
1774}
1775
1776/// The kind of a date part with its `form` attribute.
1777#[allow(missing_docs)]
1778#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1779#[serde(rename_all = "kebab-case")]
1780pub enum DatePartName {
1781    Day,
1782    Month,
1783    Year,
1784}
1785
1786/// Any allowable date part format.
1787#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1788#[serde(rename_all = "kebab-case")]
1789pub enum DateAnyForm {
1790    /// ā€œ1ā€
1791    Numeric,
1792    /// ā€œ01ā€
1793    NumericLeadingZeros,
1794    /// ā€œ1stā€
1795    Ordinal,
1796    /// ā€œJanuaryā€
1797    Long,
1798    /// ā€œJan.ā€
1799    Short,
1800}
1801
1802/// Strongly typed date part formats.
1803#[allow(missing_docs)]
1804#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
1805pub enum DateStrongAnyForm {
1806    Day(DateDayForm),
1807    Month(DateMonthForm),
1808    Year(LongShortForm),
1809}
1810
1811impl DateStrongAnyForm {
1812    /// Get a strongly typed date form for a name. Must return `Some` for valid
1813    /// CSL files.
1814    pub fn for_name(name: DatePartName, form: Option<DateAnyForm>) -> Self {
1815        match name {
1816            DatePartName::Day => {
1817                Self::Day(form.map(DateAnyForm::form_for_day).unwrap_or_default())
1818            }
1819            DatePartName::Month => {
1820                Self::Month(form.map(DateAnyForm::form_for_month).unwrap_or_default())
1821            }
1822            DatePartName::Year => {
1823                Self::Year(form.map(DateAnyForm::form_for_year).unwrap_or_default())
1824            }
1825        }
1826    }
1827}
1828
1829impl DateAnyForm {
1830    /// Retrieve the form for a day.
1831    pub fn form_for_day(self) -> DateDayForm {
1832        match self {
1833            Self::NumericLeadingZeros => DateDayForm::NumericLeadingZeros,
1834            Self::Ordinal => DateDayForm::Ordinal,
1835            _ => DateDayForm::default(),
1836        }
1837    }
1838
1839    /// Retrieve the form for a month.
1840    pub fn form_for_month(self) -> DateMonthForm {
1841        match self {
1842            Self::Short => DateMonthForm::Short,
1843            Self::Numeric => DateMonthForm::Numeric,
1844            Self::NumericLeadingZeros => DateMonthForm::NumericLeadingZeros,
1845            _ => DateMonthForm::default(),
1846        }
1847    }
1848
1849    /// Retrieve the form for a year.
1850    pub fn form_for_year(self) -> LongShortForm {
1851        match self {
1852            Self::Short => LongShortForm::Short,
1853            _ => LongShortForm::default(),
1854        }
1855    }
1856}
1857
1858/// How a day is formatted.
1859#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash, Deserialize, Serialize)]
1860#[serde(rename_all = "kebab-case")]
1861pub enum DateDayForm {
1862    /// ā€œ1ā€
1863    #[default]
1864    Numeric,
1865    /// ā€œ01ā€
1866    NumericLeadingZeros,
1867    /// ā€œ1stā€
1868    Ordinal,
1869}
1870
1871/// How a month is formatted.
1872#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash, Deserialize, Serialize)]
1873#[serde(rename_all = "kebab-case")]
1874pub enum DateMonthForm {
1875    /// ā€œJanuaryā€
1876    #[default]
1877    Long,
1878    /// ā€œJan.ā€
1879    Short,
1880    /// ā€œ1ā€
1881    Numeric,
1882    /// ā€œ01ā€
1883    NumericLeadingZeros,
1884}
1885
1886/// Whether to format something in long or short form.
1887#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1888#[serde(rename_all = "kebab-case")]
1889#[allow(missing_docs)]
1890pub enum LongShortForm {
1891    #[default]
1892    Long,
1893    Short,
1894}
1895
1896/// Renders a number.
1897#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1898#[serde(rename_all = "kebab-case")]
1899pub struct Number {
1900    /// The variable whose value is used.
1901    #[serde(rename = "@variable")]
1902    pub variable: NumberVariable,
1903    /// How the number is formatted.
1904    #[serde(rename = "@form", default)]
1905    pub form: NumberForm,
1906    /// Override formatting style.
1907    #[serde(flatten)]
1908    pub formatting: Formatting,
1909    /// Add prefix and suffix.
1910    #[serde(flatten)]
1911    pub affixes: Affixes,
1912    /// Set layout level.
1913    #[serde(rename = "@display")]
1914    #[serde(skip_serializing_if = "Option::is_none")]
1915    pub display: Option<Display>,
1916    /// Transform the text case.
1917    #[serde(rename = "@text-case")]
1918    #[serde(skip_serializing_if = "Option::is_none")]
1919    pub text_case: Option<TextCase>,
1920}
1921
1922to_formatting!(Number);
1923to_affixes!(Number);
1924
1925/// How a number is formatted.
1926#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1927#[serde(rename_all = "kebab-case")]
1928pub enum NumberForm {
1929    /// ā€œ1ā€
1930    #[default]
1931    Numeric,
1932    /// ā€œ1stā€
1933    Ordinal,
1934    /// ā€œfirstā€
1935    LongOrdinal,
1936    /// ā€œIā€
1937    Roman,
1938}
1939
1940/// Renders a list of names.
1941#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
1942#[serde(rename_all = "kebab-case")]
1943pub struct Names {
1944    /// The variable whose value is used.
1945    #[serde(rename = "@variable", default)]
1946    pub variable: Vec<NameVariable>,
1947    /// Child elements.
1948    #[serde(rename = "$value", default)]
1949    pub children: Vec<NamesChild>,
1950    /// Delimiter between names.
1951    #[serde(rename = "@delimiter")]
1952    #[serde(skip_serializing_if = "Option::is_none")]
1953    delimiter: Option<String>,
1954
1955    /// Delimiter between second-to-last and last name.
1956    #[serde(rename = "@and")]
1957    #[serde(skip_serializing_if = "Option::is_none")]
1958    pub and: Option<NameAnd>,
1959    /// Delimiter before et al.
1960    #[serde(rename = "@delimiter-precedes-et-al")]
1961    #[serde(skip_serializing_if = "Option::is_none")]
1962    pub delimiter_precedes_et_al: Option<DelimiterBehavior>,
1963    /// Whether to use the delimiter before the last name.
1964    #[serde(rename = "@delimiter-precedes-last")]
1965    #[serde(skip_serializing_if = "Option::is_none")]
1966    pub delimiter_precedes_last: Option<DelimiterBehavior>,
1967    /// Minimum number of names to use et al.
1968    #[serde(rename = "@et-al-min", deserialize_with = "deserialize_u32_option", default)]
1969    #[serde(skip_serializing_if = "Option::is_none")]
1970    pub et_al_min: Option<u32>,
1971    /// Maximum number of names to use before et al.
1972    #[serde(
1973        rename = "@et-al-use-first",
1974        deserialize_with = "deserialize_u32_option",
1975        default
1976    )]
1977    #[serde(skip_serializing_if = "Option::is_none")]
1978    pub et_al_use_first: Option<u32>,
1979    /// Minimum number of names to use et al. for repeated citations.
1980    #[serde(
1981        rename = "@et-al-subsequent-min",
1982        deserialize_with = "deserialize_u32_option",
1983        default
1984    )]
1985    #[serde(skip_serializing_if = "Option::is_none")]
1986    pub et_al_subsequent_min: Option<u32>,
1987    /// Maximum number of names to use before et al. for repeated citations.
1988    #[serde(
1989        rename = "@et-al-subsequent-use-first",
1990        deserialize_with = "deserialize_u32_option",
1991        default
1992    )]
1993    #[serde(skip_serializing_if = "Option::is_none")]
1994    pub et_al_subsequent_use_first: Option<u32>,
1995    /// Whether to use the last name in the author list when there are at least
1996    /// `et_al_min` names.
1997    #[serde(
1998        rename = "@et-al-use-last",
1999        deserialize_with = "deserialize_bool_option",
2000        default
2001    )]
2002    #[serde(skip_serializing_if = "Option::is_none")]
2003    pub et_al_use_last: Option<bool>,
2004    /// Which name parts to display for personal names.
2005    #[serde(rename = "@name-form")]
2006    #[serde(skip_serializing_if = "Option::is_none")]
2007    pub name_form: Option<NameForm>,
2008    /// Whether to initialize the first name if `initialize-with` is Some.
2009    #[serde(
2010        rename = "@initialize",
2011        deserialize_with = "deserialize_bool_option",
2012        default
2013    )]
2014    #[serde(skip_serializing_if = "Option::is_none")]
2015    pub initialize: Option<bool>,
2016    /// String to initialize the first name with.
2017    #[serde(rename = "@initialize-with")]
2018    #[serde(skip_serializing_if = "Option::is_none")]
2019    pub initialize_with: Option<String>,
2020    /// Whether to turn the name around.
2021    #[serde(rename = "@name-as-sort-order")]
2022    #[serde(skip_serializing_if = "Option::is_none")]
2023    pub name_as_sort_order: Option<NameAsSortOrder>,
2024    /// Delimiter between given name and first name. Only used if
2025    /// `name-as-sort-order` is Some.
2026    #[serde(rename = "@sort-separator")]
2027    #[serde(skip_serializing_if = "Option::is_none")]
2028    pub sort_separator: Option<String>,
2029
2030    /// Set the font style.
2031    #[serde(rename = "@font-style")]
2032    #[serde(skip_serializing_if = "Option::is_none")]
2033    pub font_style: Option<FontStyle>,
2034    /// Choose normal or small caps.
2035    #[serde(rename = "@font-variant")]
2036    #[serde(skip_serializing_if = "Option::is_none")]
2037    pub font_variant: Option<FontVariant>,
2038    /// Set the font weight.
2039    #[serde(rename = "@font-weight")]
2040    #[serde(skip_serializing_if = "Option::is_none")]
2041    pub font_weight: Option<FontWeight>,
2042    /// Choose underlining.
2043    #[serde(rename = "@text-decoration")]
2044    #[serde(skip_serializing_if = "Option::is_none")]
2045    pub text_decoration: Option<TextDecoration>,
2046    /// Choose vertical alignment.
2047    #[serde(rename = "@vertical-align")]
2048    #[serde(skip_serializing_if = "Option::is_none")]
2049    pub vertical_align: Option<VerticalAlign>,
2050
2051    /// The prefix.
2052    #[serde(rename = "@prefix")]
2053    #[serde(skip_serializing_if = "Option::is_none")]
2054    pub prefix: Option<String>,
2055    /// The suffix.
2056    #[serde(rename = "@suffix")]
2057    #[serde(skip_serializing_if = "Option::is_none")]
2058    pub suffix: Option<String>,
2059
2060    /// Set layout level.
2061    #[serde(rename = "@display")]
2062    #[serde(skip_serializing_if = "Option::is_none")]
2063    pub display: Option<Display>,
2064}
2065
2066impl Names {
2067    /// Return names with default settings and the given variables.
2068    pub fn with_variables(variables: Vec<NameVariable>) -> Self {
2069        Self {
2070            variable: variables,
2071            children: Vec::default(),
2072            delimiter: None,
2073
2074            and: None,
2075            delimiter_precedes_et_al: None,
2076            delimiter_precedes_last: None,
2077            et_al_min: None,
2078            et_al_use_first: None,
2079            et_al_subsequent_min: None,
2080            et_al_subsequent_use_first: None,
2081            et_al_use_last: None,
2082            name_form: None,
2083            initialize: None,
2084            initialize_with: None,
2085            name_as_sort_order: None,
2086            sort_separator: None,
2087
2088            font_style: None,
2089            font_variant: None,
2090            font_weight: None,
2091            text_decoration: None,
2092            vertical_align: None,
2093
2094            prefix: None,
2095            suffix: None,
2096
2097            display: None,
2098        }
2099    }
2100
2101    /// Return the delimiter given some name options.
2102    pub fn delimiter<'a>(&'a self, name_options: &'a InheritableNameOptions) -> &'a str {
2103        self.delimiter
2104            .as_deref()
2105            .or(name_options.name_delimiter.as_deref())
2106            .unwrap_or_default()
2107    }
2108
2109    /// Return the name element.
2110    pub fn name(&self) -> Option<&Name> {
2111        self.children.iter().find_map(|c| match c {
2112            NamesChild::Name(n) => Some(n),
2113            _ => None,
2114        })
2115    }
2116
2117    /// Return the et-al element.
2118    pub fn et_al(&self) -> Option<&EtAl> {
2119        self.children.iter().find_map(|c| match c {
2120            NamesChild::EtAl(e) => Some(e),
2121            _ => None,
2122        })
2123    }
2124
2125    /// Return the label element.
2126    pub fn label(&self) -> Option<(&VariablelessLabel, NameLabelPosition)> {
2127        let mut pos = NameLabelPosition::BeforeName;
2128        self.children.iter().find_map(|c| match c {
2129            NamesChild::Label(l) => Some((l, pos)),
2130            NamesChild::Name(_) => {
2131                pos = NameLabelPosition::AfterName;
2132                None
2133            }
2134            _ => None,
2135        })
2136    }
2137
2138    /// Return the substitute element.
2139    pub fn substitute(&self) -> Option<&Substitute> {
2140        self.children.iter().find_map(|c| match c {
2141            NamesChild::Substitute(s) => Some(s),
2142            _ => None,
2143        })
2144    }
2145
2146    /// Return the inheritable name options.
2147    pub fn options(&self) -> InheritableNameOptions {
2148        InheritableNameOptions {
2149            and: self.and,
2150            delimiter_precedes_et_al: self.delimiter_precedes_et_al,
2151            delimiter_precedes_last: self.delimiter_precedes_last,
2152            et_al_min: self.et_al_min,
2153            et_al_use_first: self.et_al_use_first,
2154            et_al_subsequent_min: self.et_al_subsequent_min,
2155            et_al_subsequent_use_first: self.et_al_subsequent_use_first,
2156            et_al_use_last: self.et_al_use_last,
2157            name_form: self.name_form,
2158            initialize: self.initialize,
2159            initialize_with: self.initialize_with.clone(),
2160            name_as_sort_order: self.name_as_sort_order,
2161            sort_separator: self.sort_separator.clone(),
2162            name_delimiter: None,
2163            names_delimiter: self.delimiter.clone(),
2164        }
2165    }
2166
2167    /// Convert a [`Names`] within a substitute to a name using the parent element.
2168    pub fn from_names_substitute(&self, child: &Self) -> Names {
2169        if child.name().is_some()
2170            || child.et_al().is_some()
2171            || child.substitute().is_some()
2172        {
2173            return child.clone();
2174        }
2175
2176        let formatting = child.to_formatting().apply(self.to_formatting());
2177        let options = self.options().apply(&child.options());
2178
2179        Names {
2180            variable: if child.variable.is_empty() {
2181                self.variable.clone()
2182            } else {
2183                child.variable.clone()
2184            },
2185            children: self
2186                .children
2187                .iter()
2188                .filter(|c| !matches!(c, NamesChild::Substitute(_)))
2189                .cloned()
2190                .collect(),
2191            delimiter: child.delimiter.clone().or_else(|| self.delimiter.clone()),
2192
2193            and: options.and,
2194            delimiter_precedes_et_al: options.delimiter_precedes_et_al,
2195            delimiter_precedes_last: options.delimiter_precedes_last,
2196            et_al_min: options.et_al_min,
2197            et_al_use_first: options.et_al_use_first,
2198            et_al_subsequent_min: options.et_al_subsequent_min,
2199            et_al_subsequent_use_first: options.et_al_subsequent_use_first,
2200            et_al_use_last: options.et_al_use_last,
2201            name_form: options.name_form,
2202            initialize: options.initialize,
2203            initialize_with: options.initialize_with,
2204            name_as_sort_order: options.name_as_sort_order,
2205            sort_separator: options.sort_separator,
2206
2207            font_style: formatting.font_style,
2208            font_variant: formatting.font_variant,
2209            font_weight: formatting.font_weight,
2210            text_decoration: formatting.text_decoration,
2211            vertical_align: formatting.vertical_align,
2212
2213            prefix: child.prefix.clone().or_else(|| self.prefix.clone()),
2214            suffix: child.suffix.clone().or_else(|| self.suffix.clone()),
2215            display: child.display.or(self.display),
2216        }
2217    }
2218}
2219
2220to_formatting!(Names, self);
2221to_affixes!(Names, self);
2222
2223/// Where the `cs:label` element within a `cs:names` element appeared relative
2224/// to `cs:name`.
2225#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
2226pub enum NameLabelPosition {
2227    /// The label appeared after the name element.
2228    AfterName,
2229    /// The label appeared before the name element.
2230    BeforeName,
2231}
2232
2233/// Possible children for a `cs:names` element.
2234#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2235#[serde(rename_all = "kebab-case")]
2236pub enum NamesChild {
2237    /// A `cs:name` element.
2238    Name(Name),
2239    /// A `cs:et-al` element.
2240    EtAl(EtAl),
2241    /// A `cs:label` element.
2242    Label(VariablelessLabel),
2243    /// A `cs:substitute` element.
2244    Substitute(Substitute),
2245}
2246
2247/// Configuration of how to print names.
2248#[derive(Debug, Default, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2249#[serde(rename_all = "kebab-case", default)]
2250pub struct Name {
2251    /// Delimiter between names.
2252    #[serde(rename = "@delimiter")]
2253    #[serde(skip_serializing_if = "Option::is_none")]
2254    delimiter: Option<String>,
2255    /// Which name parts to display for personal names.
2256    #[serde(rename = "@form")]
2257    #[serde(skip_serializing_if = "Option::is_none")]
2258    pub form: Option<NameForm>,
2259    /// Name parts for formatting for the given and family name.
2260    #[serde(rename = "name-part")]
2261    parts: Vec<NamePart>,
2262    /// Options for this name.
2263    #[serde(flatten)]
2264    options: InheritableNameOptions,
2265    /// Override formatting style.
2266    #[serde(flatten)]
2267    pub formatting: Formatting,
2268    /// Add prefix and suffix.
2269    #[serde(flatten)]
2270    pub affixes: Affixes,
2271}
2272
2273to_formatting!(Name);
2274to_affixes!(Name);
2275
2276impl Name {
2277    /// Retrieve [`NamePart`] configuration for the given name.
2278    pub fn name_part_given(&self) -> Option<&NamePart> {
2279        self.parts.iter().find(|p| p.name == NamePartName::Given)
2280    }
2281
2282    /// Retrieve [`NamePart`] configuration for the family name.
2283    pub fn name_part_family(&self) -> Option<&NamePart> {
2284        self.parts.iter().find(|p| p.name == NamePartName::Family)
2285    }
2286
2287    /// Retrieve the [`NameOptions`] for this name.
2288    pub fn options<'s>(&'s self, inherited: &'s InheritableNameOptions) -> NameOptions {
2289        let applied = inherited.apply(&self.options);
2290        NameOptions {
2291            and: applied.and,
2292            delimiter: self
2293                .delimiter
2294                .as_deref()
2295                .or(inherited.name_delimiter.as_deref())
2296                .unwrap_or(", "),
2297            delimiter_precedes_et_al: applied
2298                .delimiter_precedes_et_al
2299                .unwrap_or_default(),
2300            delimiter_precedes_last: applied.delimiter_precedes_last.unwrap_or_default(),
2301            et_al_min: applied.et_al_min,
2302            et_al_use_first: applied.et_al_use_first,
2303            et_al_subsequent_min: applied.et_al_subsequent_min,
2304            et_al_subsequent_use_first: applied.et_al_subsequent_use_first,
2305            et_al_use_last: applied.et_al_use_last.unwrap_or_default(),
2306            form: self.form.or(inherited.name_form).unwrap_or_default(),
2307            initialize: applied.initialize.unwrap_or(true),
2308            initialize_with: self
2309                .options
2310                .initialize_with
2311                .as_deref()
2312                .or(inherited.initialize_with.as_deref()),
2313            name_as_sort_order: applied.name_as_sort_order,
2314            sort_separator: self
2315                .options
2316                .sort_separator
2317                .as_deref()
2318                .or(inherited.sort_separator.as_deref())
2319                .unwrap_or(", "),
2320        }
2321    }
2322}
2323
2324/// Global configuration of how to print names.
2325#[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Deserialize, Serialize)]
2326#[serde(default)]
2327pub struct InheritableNameOptions {
2328    /// Delimiter between second-to-last and last name.
2329    #[serde(rename = "@and")]
2330    #[serde(skip_serializing_if = "Option::is_none")]
2331    pub and: Option<NameAnd>,
2332    /// Delimiter inherited to `cs:name` elements.
2333    #[serde(rename = "@name-delimiter")]
2334    #[serde(skip_serializing_if = "Option::is_none")]
2335    pub name_delimiter: Option<String>,
2336    /// Delimiter inherited to `cs:names` elements.
2337    #[serde(rename = "@names-delimiter")]
2338    #[serde(skip_serializing_if = "Option::is_none")]
2339    pub names_delimiter: Option<String>,
2340    /// Delimiter before et al.
2341    #[serde(rename = "@delimiter-precedes-et-al")]
2342    #[serde(skip_serializing_if = "Option::is_none")]
2343    pub delimiter_precedes_et_al: Option<DelimiterBehavior>,
2344    /// Whether to use the delimiter before the last name.
2345    #[serde(rename = "@delimiter-precedes-last")]
2346    #[serde(skip_serializing_if = "Option::is_none")]
2347    pub delimiter_precedes_last: Option<DelimiterBehavior>,
2348    /// Minimum number of names to use et al.
2349    #[serde(rename = "@et-al-min", deserialize_with = "deserialize_u32_option", default)]
2350    #[serde(skip_serializing_if = "Option::is_none")]
2351    pub et_al_min: Option<u32>,
2352    /// Maximum number of names to use before et al.
2353    #[serde(
2354        rename = "@et-al-use-first",
2355        deserialize_with = "deserialize_u32_option",
2356        default
2357    )]
2358    #[serde(skip_serializing_if = "Option::is_none")]
2359    pub et_al_use_first: Option<u32>,
2360    /// Minimum number of names to use et al. for repeated citations.
2361    #[serde(
2362        rename = "@et-al-subsequent-min",
2363        deserialize_with = "deserialize_u32_option",
2364        default
2365    )]
2366    #[serde(skip_serializing_if = "Option::is_none")]
2367    pub et_al_subsequent_min: Option<u32>,
2368    /// Maximum number of names to use before et al. for repeated citations.
2369    #[serde(
2370        rename = "@et-al-subsequent-use-first",
2371        deserialize_with = "deserialize_u32_option",
2372        default
2373    )]
2374    #[serde(skip_serializing_if = "Option::is_none")]
2375    pub et_al_subsequent_use_first: Option<u32>,
2376    /// Whether to use the last name in the author list when there are at least
2377    /// `et_al_min` names.
2378    #[serde(
2379        rename = "@et-al-use-last",
2380        deserialize_with = "deserialize_bool_option",
2381        default
2382    )]
2383    #[serde(skip_serializing_if = "Option::is_none")]
2384    pub et_al_use_last: Option<bool>,
2385    /// Which name parts to display for personal names.
2386    #[serde(rename = "@name-form")]
2387    #[serde(skip_serializing_if = "Option::is_none")]
2388    pub name_form: Option<NameForm>,
2389    /// Whether to initialize the first name if `initialize-with` is Some.
2390    #[serde(
2391        rename = "@initialize",
2392        deserialize_with = "deserialize_bool_option",
2393        default
2394    )]
2395    #[serde(skip_serializing_if = "Option::is_none")]
2396    pub initialize: Option<bool>,
2397    /// String to initialize the first name with.
2398    #[serde(rename = "@initialize-with")]
2399    #[serde(skip_serializing_if = "Option::is_none")]
2400    pub initialize_with: Option<String>,
2401    /// Whether to turn the name around.
2402    #[serde(rename = "@name-as-sort-order")]
2403    #[serde(skip_serializing_if = "Option::is_none")]
2404    pub name_as_sort_order: Option<NameAsSortOrder>,
2405    /// Delimiter between given name and first name. Only used if
2406    /// `name-as-sort-order` is Some.
2407    #[serde(rename = "@sort-separator")]
2408    #[serde(skip_serializing_if = "Option::is_none")]
2409    pub sort_separator: Option<String>,
2410}
2411
2412/// Definite name options. Obtain from [`Name::options`] using
2413/// [`InheritableNameOptions`].
2414pub struct NameOptions<'s> {
2415    /// Delimiter between second-to-last and last name.
2416    pub and: Option<NameAnd>,
2417    /// Delimiter to separate names.
2418    pub delimiter: &'s str,
2419    /// Delimiter before et al.
2420    pub delimiter_precedes_et_al: DelimiterBehavior,
2421    /// Whether to use the delimiter before the last name.
2422    pub delimiter_precedes_last: DelimiterBehavior,
2423    /// Minimum number of names to use et al.
2424    pub et_al_min: Option<u32>,
2425    /// Maximum number of names to use before et al.
2426    pub et_al_use_first: Option<u32>,
2427    /// Minimum number of names to use et al. for repeated citations.
2428    pub et_al_subsequent_min: Option<u32>,
2429    /// Maximum number of names to use before et al. for repeated citations.
2430    pub et_al_subsequent_use_first: Option<u32>,
2431    /// Whether to use the last name in the author list when there are at least
2432    /// `et_al_min` names.
2433    pub et_al_use_last: bool,
2434    /// Which name parts to display for personal names.
2435    pub form: NameForm,
2436    /// Whether to initialize the first name if `initialize-with` is Some.
2437    pub initialize: bool,
2438    /// String to initialize the first name with.
2439    pub initialize_with: Option<&'s str>,
2440    /// Whether to turn the name around.
2441    pub name_as_sort_order: Option<NameAsSortOrder>,
2442    /// Delimiter between given name and first name. Only used if
2443    /// `name-as-sort-order` is Some.
2444    pub sort_separator: &'s str,
2445}
2446
2447impl InheritableNameOptions {
2448    /// Apply the child options to the parent options.
2449    pub fn apply(&self, child: &Self) -> Self {
2450        Self {
2451            and: child.and.or(self.and),
2452            name_delimiter: child
2453                .name_delimiter
2454                .clone()
2455                .or_else(|| self.name_delimiter.clone()),
2456            names_delimiter: child
2457                .names_delimiter
2458                .clone()
2459                .or_else(|| self.names_delimiter.clone()),
2460            delimiter_precedes_et_al: child
2461                .delimiter_precedes_et_al
2462                .or(self.delimiter_precedes_et_al),
2463            delimiter_precedes_last: child
2464                .delimiter_precedes_last
2465                .or(self.delimiter_precedes_last),
2466            et_al_min: child.et_al_min.or(self.et_al_min),
2467            et_al_use_first: child.et_al_use_first.or(self.et_al_use_first),
2468            et_al_subsequent_min: child
2469                .et_al_subsequent_min
2470                .or(self.et_al_subsequent_min),
2471            et_al_subsequent_use_first: child
2472                .et_al_subsequent_use_first
2473                .or(self.et_al_subsequent_use_first),
2474            et_al_use_last: child.et_al_use_last.or(self.et_al_use_last),
2475            name_form: child.name_form.or(self.name_form),
2476            initialize: child.initialize.or(self.initialize),
2477            initialize_with: child
2478                .initialize_with
2479                .clone()
2480                .or_else(|| self.initialize_with.clone()),
2481            name_as_sort_order: child.name_as_sort_order.or(self.name_as_sort_order),
2482            sort_separator: child
2483                .sort_separator
2484                .clone()
2485                .or_else(|| self.sort_separator.clone()),
2486        }
2487    }
2488}
2489
2490impl NameOptions<'_> {
2491    /// Whether the nth name is suppressed given the number of names and this
2492    /// configuration.
2493    pub fn is_suppressed(&self, idx: usize, length: usize, is_subsequent: bool) -> bool {
2494        // This is not suppressed if we print the last element and this is it.
2495        if self.et_al_use_last && idx + 1 >= length {
2496            return false;
2497        }
2498
2499        // If this is a subsequent citation of the same item, use other CSL options, if they exist
2500        let (et_al_min, et_al_use_first) = if is_subsequent {
2501            (
2502                self.et_al_subsequent_min.or(self.et_al_min),
2503                self.et_al_subsequent_use_first.or(self.et_al_use_first),
2504            )
2505        } else {
2506            (self.et_al_min, self.et_al_use_first)
2507        };
2508
2509        let et_al_min = et_al_min.map_or(usize::MAX, |u| u as usize);
2510        let et_al_use_first = et_al_use_first.map_or(usize::MAX, |u| u as usize);
2511
2512        length >= et_al_min && idx + 1 > et_al_use_first
2513    }
2514}
2515
2516/// How to render the delimiter before the last name.
2517#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2518#[serde(rename_all = "kebab-case")]
2519pub enum NameAnd {
2520    /// Use the string "and".
2521    Text,
2522    /// Use the ampersand character.
2523    Symbol,
2524}
2525
2526/// When delimiters shall be inserted.
2527#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2528#[serde(rename_all = "kebab-case")]
2529pub enum DelimiterBehavior {
2530    /// Only used for lists with more than one (`-precedes-et-al`) or two
2531    /// (`-precedes-last`) names.
2532    #[default]
2533    Contextual,
2534    /// Only use if the preceding name is inverted (per `name-as-sort-order`).
2535    AfterInvertedName,
2536    /// Always use the delimiter for this condition.
2537    Always,
2538    /// Never use the delimiter for this condition.
2539    Never,
2540}
2541
2542/// How many name parts to print.
2543#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2544#[serde(rename_all = "kebab-case")]
2545pub enum NameForm {
2546    /// Print all name parts
2547    #[default]
2548    Long,
2549    /// Print only the family name part and non-dropping-particle.
2550    Short,
2551    /// Count the total number of names.
2552    Count,
2553}
2554
2555/// In which order to print the names.
2556#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2557#[serde(rename_all = "kebab-case")]
2558pub enum NameAsSortOrder {
2559    /// Only the first name is turned around.
2560    First,
2561    /// All names are turned around.
2562    All,
2563}
2564
2565/// How to format a given name part.
2566#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2567#[serde(rename_all = "kebab-case")]
2568pub struct NamePart {
2569    /// Which name part this applies to.
2570    #[serde(rename = "@name")]
2571    pub name: NamePartName,
2572    /// Override formatting style.
2573    #[serde(flatten)]
2574    pub formatting: Formatting,
2575    /// Add prefix and suffix.
2576    #[serde(flatten)]
2577    pub affixes: Affixes,
2578    /// Transform the text case.
2579    #[serde(rename = "@text-case")]
2580    #[serde(skip_serializing_if = "Option::is_none")]
2581    pub text_case: Option<TextCase>,
2582}
2583
2584to_formatting!(NamePart);
2585to_affixes!(NamePart);
2586
2587/// Which part of the name a [`NamePart`] applies to.
2588#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2589#[serde(rename_all = "kebab-case")]
2590pub enum NamePartName {
2591    /// The given name.
2592    Given,
2593    /// The family name.
2594    Family,
2595}
2596
2597/// Configure the et al. abbreviation.
2598#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash, Deserialize, Serialize)]
2599pub struct EtAl {
2600    /// Which term to use.
2601    #[serde(rename = "@term", default)]
2602    pub term: EtAlTerm,
2603    /// Override formatting style.
2604    #[serde(flatten)]
2605    pub formatting: Formatting,
2606}
2607
2608to_formatting!(EtAl);
2609
2610/// Which term to use for et al.
2611#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2612pub enum EtAlTerm {
2613    /// ā€œet al.ā€
2614    #[default]
2615    #[serde(rename = "et al", alias = "et-al")]
2616    EtAl,
2617    /// ā€œand othersā€
2618    #[serde(rename = "and others", alias = "and-others")]
2619    AndOthers,
2620}
2621
2622impl From<EtAlTerm> for Term {
2623    fn from(term: EtAlTerm) -> Self {
2624        match term {
2625            EtAlTerm::EtAl => Term::Other(OtherTerm::EtAl),
2626            EtAlTerm::AndOthers => Term::Other(OtherTerm::AndOthers),
2627        }
2628    }
2629}
2630
2631/// What to do if the name variable is empty.
2632#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2633pub struct Substitute {
2634    /// The layout to use instead.
2635    #[serde(rename = "$value")]
2636    pub children: Vec<LayoutRenderingElement>,
2637}
2638
2639/// Print a label for a number variable.
2640#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2641pub struct Label {
2642    /// The variable for which to print the label.
2643    #[serde(rename = "@variable")]
2644    pub variable: NumberOrPageVariable,
2645    /// The form of the label.
2646    #[serde(flatten)]
2647    pub label: VariablelessLabel,
2648}
2649
2650/// A label without its variable.
2651#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2652pub struct VariablelessLabel {
2653    /// What variant of label is chosen.
2654    #[serde(rename = "@form", default)]
2655    pub form: TermForm,
2656    /// How to pluiralize the label.
2657    #[serde(rename = "@plural", default)]
2658    pub plural: LabelPluralize,
2659    /// Override formatting style.
2660    #[serde(flatten)]
2661    pub formatting: Formatting,
2662    /// Add prefix and suffix.
2663    #[serde(flatten)]
2664    pub affixes: Affixes,
2665    /// Transform the text case.
2666    #[serde(rename = "@text-case")]
2667    #[serde(skip_serializing_if = "Option::is_none")]
2668    pub text_case: Option<TextCase>,
2669    /// Remove periods from the output.
2670    ///
2671    /// Default: `false`
2672    #[serde(rename = "@strip-periods", default, deserialize_with = "deserialize_bool")]
2673    pub strip_periods: bool,
2674}
2675
2676to_formatting!(VariablelessLabel);
2677to_affixes!(VariablelessLabel);
2678
2679/// How to pluralize a label.
2680#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2681#[serde(rename_all = "kebab-case")]
2682pub enum LabelPluralize {
2683    /// Match plurality of the variable.
2684    #[default]
2685    Contextual,
2686    /// Always use the plural form.
2687    Always,
2688    /// Always use the singular form.
2689    Never,
2690}
2691
2692/// A group of formatting instructions that is only shown if no variable is
2693/// referenced or at least one referenced variable is populated.
2694#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2695pub struct Group {
2696    /// The formatting instructions.
2697    #[serde(rename = "$value")]
2698    pub children: Vec<LayoutRenderingElement>,
2699    // Formatting and affixes fields are rolled into this because
2700    // #[serde(flatten)] doesn't work with $value fields.
2701    /// Set the font style.
2702    #[serde(rename = "@font-style")]
2703    #[serde(skip_serializing_if = "Option::is_none")]
2704    pub font_style: Option<FontStyle>,
2705    /// Choose normal or small caps.
2706    #[serde(rename = "@font-variant")]
2707    #[serde(skip_serializing_if = "Option::is_none")]
2708    pub font_variant: Option<FontVariant>,
2709    /// Set the font weight.
2710    #[serde(rename = "@font-weight")]
2711    #[serde(skip_serializing_if = "Option::is_none")]
2712    pub font_weight: Option<FontWeight>,
2713    /// Choose underlining.
2714    #[serde(rename = "@text-decoration")]
2715    #[serde(skip_serializing_if = "Option::is_none")]
2716    pub text_decoration: Option<TextDecoration>,
2717    /// Choose vertical alignment.
2718    #[serde(rename = "@vertical-align")]
2719    #[serde(skip_serializing_if = "Option::is_none")]
2720    pub vertical_align: Option<VerticalAlign>,
2721    /// The prefix.
2722    #[serde(rename = "@prefix")]
2723    #[serde(skip_serializing_if = "Option::is_none")]
2724    pub prefix: Option<String>,
2725    /// The suffix.
2726    #[serde(rename = "@suffix")]
2727    #[serde(skip_serializing_if = "Option::is_none")]
2728    pub suffix: Option<String>,
2729    /// Delimit pieces of the output.
2730    #[serde(rename = "@delimiter")]
2731    #[serde(skip_serializing_if = "Option::is_none")]
2732    pub delimiter: Option<String>,
2733    /// Set layout level.
2734    #[serde(rename = "@display")]
2735    #[serde(skip_serializing_if = "Option::is_none")]
2736    pub display: Option<Display>,
2737}
2738
2739to_formatting!(Group, self);
2740to_affixes!(Group, self);
2741
2742/// A conditional group of formatting instructions.
2743#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2744pub struct Choose {
2745    /// If branch of the conditional group.
2746    #[serde(rename = "if")]
2747    pub if_: ChooseBranch,
2748    /// Other branches of the conditional group. The first matching branch is used.
2749    #[serde(rename = "else-if")]
2750    #[serde(default)]
2751    pub else_if: Vec<ChooseBranch>,
2752    /// The formatting instructions to use if no branch matches.
2753    #[serde(rename = "else")]
2754    #[serde(skip_serializing_if = "Option::is_none")]
2755    pub otherwise: Option<ElseBranch>,
2756}
2757
2758impl Choose {
2759    /// Return an iterator over all branches with a condition.
2760    pub fn branches(&self) -> impl Iterator<Item = &ChooseBranch> {
2761        std::iter::once(&self.if_).chain(self.else_if.iter())
2762    }
2763
2764    /// Find the child element that renders the given variable.
2765    pub fn find_variable_element(
2766        &self,
2767        variable: Variable,
2768        macros: &[CslMacro],
2769    ) -> Option<LayoutRenderingElement> {
2770        self.branches()
2771            .find_map(|b| {
2772                b.children
2773                    .iter()
2774                    .find_map(|c| c.find_variable_element(variable, macros))
2775            })
2776            .clone()
2777    }
2778}
2779
2780/// A single branch of a conditional group.
2781#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2782pub struct ChooseBranch {
2783    /// Other than this choose, two elements would result in the same
2784    /// rendering.
2785    #[serde(
2786        rename = "@disambiguate",
2787        deserialize_with = "deserialize_bool_option",
2788        default
2789    )]
2790    #[serde(skip_serializing_if = "Option::is_none")]
2791    pub disambiguate: Option<bool>,
2792    /// The variable contains numeric data.
2793    #[serde(rename = "@is-numeric")]
2794    /// The variable contains an approximate date.
2795    #[serde(skip_serializing_if = "Option::is_none")]
2796    pub is_numeric: Option<Vec<Variable>>,
2797    /// The variable contains an approximate date.
2798    #[serde(rename = "@is-uncertain-date")]
2799    #[serde(skip_serializing_if = "Option::is_none")]
2800    pub is_uncertain_date: Option<Vec<DateVariable>>,
2801    /// The locator matches the given type.
2802    #[serde(rename = "@locator")]
2803    #[serde(skip_serializing_if = "Option::is_none")]
2804    pub locator: Option<Vec<Locator>>,
2805    /// Tests the position of this citation in the citations to the same item.
2806    /// Only ever true for citations.
2807    #[serde(rename = "@position")]
2808    #[serde(skip_serializing_if = "Option::is_none")]
2809    pub position: Option<Vec<TestPosition>>,
2810    /// Tests whether the item is of a certain type.
2811    #[serde(rename = "@type")]
2812    #[serde(skip_serializing_if = "Option::is_none")]
2813    pub type_: Option<Vec<Kind>>,
2814    /// Tests whether the default form of this variable is non-empty.
2815    #[serde(rename = "@variable")]
2816    #[serde(skip_serializing_if = "Option::is_none")]
2817    pub variable: Option<Vec<Variable>>,
2818    /// How to handle the set of tests.
2819    #[serde(rename = "@match")]
2820    #[serde(default)]
2821    pub match_: ChooseMatch,
2822    #[serde(rename = "$value", default)]
2823    /// The formatting instructions to use if the condition matches.
2824    pub children: Vec<LayoutRenderingElement>,
2825}
2826
2827impl ChooseBranch {
2828    /// Retrieve the test of this branch. Valid CSL files must return `Some`
2829    /// here.
2830    pub fn test(&self) -> Option<ChooseTest> {
2831        if let Some(disambiguate) = self.disambiguate {
2832            if !disambiguate {
2833                None
2834            } else {
2835                Some(ChooseTest::Disambiguate)
2836            }
2837        } else if let Some(is_numeric) = &self.is_numeric {
2838            Some(ChooseTest::IsNumeric(is_numeric))
2839        } else if let Some(is_uncertain_date) = &self.is_uncertain_date {
2840            Some(ChooseTest::IsUncertainDate(is_uncertain_date))
2841        } else if let Some(locator) = &self.locator {
2842            Some(ChooseTest::Locator(locator))
2843        } else if let Some(position) = &self.position {
2844            Some(ChooseTest::Position(position))
2845        } else if let Some(type_) = &self.type_ {
2846            Some(ChooseTest::Type(type_))
2847        } else {
2848            self.variable.as_ref().map(|variable| ChooseTest::Variable(variable))
2849        }
2850    }
2851}
2852
2853/// The formatting instructions to use if no branch matches.
2854#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2855pub struct ElseBranch {
2856    /// The formatting instructions.
2857    #[serde(rename = "$value")]
2858    pub children: Vec<LayoutRenderingElement>,
2859}
2860
2861/// A single test in a conditional group.
2862#[derive(Debug, Clone, Eq, PartialEq, Hash)]
2863pub enum ChooseTest<'a> {
2864    /// Other than this choose, two elements would result in the same
2865    /// rendering.
2866    Disambiguate,
2867    /// The variable contains numeric data.
2868    IsNumeric(&'a [Variable]),
2869    /// The variable contains an approximate date.
2870    IsUncertainDate(&'a [DateVariable]),
2871    /// The locator matches the given type.
2872    Locator(&'a [Locator]),
2873    /// Tests the position of this citation in the citations to the same item.
2874    /// Only ever true for citations.
2875    Position(&'a [TestPosition]),
2876    /// Tests whether the item is of a certain type.
2877    Type(&'a [Kind]),
2878    /// Tests whether the default form of this variable is non-empty.
2879    Variable(&'a [Variable]),
2880}
2881
2882/// Possible positions of a citation in the citations to the same item.
2883#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2884#[serde(rename_all = "kebab-case")]
2885pub enum TestPosition {
2886    /// The first citation to the item.
2887    First,
2888    /// Previously cited.
2889    Subsequent,
2890    /// Directly following a citation to the same item but the locators don't necessarily match.
2891    IbidWithLocator,
2892    /// Directly following a citation to the same item with the same locators.
2893    Ibid,
2894    /// Other citation within `near-note-distance` of the same item.
2895    NearNote,
2896}
2897
2898/// How to handle the set of tests in a conditional group.
2899#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2900#[serde(rename_all = "kebab-case")]
2901pub enum ChooseMatch {
2902    /// All tests must match.
2903    #[default]
2904    All,
2905    /// At least one test must match.
2906    Any,
2907    /// No test must match.
2908    None,
2909}
2910
2911impl ChooseMatch {
2912    /// Check whether the iterator of tests is true for this match type.
2913    pub fn test(self, mut tests: impl Iterator<Item = bool>) -> bool {
2914        match self {
2915            Self::All => tests.all(|t| t),
2916            Self::Any => tests.any(|t| t),
2917            Self::None => tests.all(|t| !t),
2918        }
2919    }
2920}
2921
2922/// A reusable set of formatting instructions.
2923#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2924pub struct CslMacro {
2925    /// The name of the macro.
2926    #[serde(rename = "@name")]
2927    pub name: String,
2928    /// The formatting instructions.
2929    #[serde(rename = "$value")]
2930    #[serde(default)]
2931    pub children: Vec<LayoutRenderingElement>,
2932}
2933
2934/// Root element of a locale file.
2935#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2936#[serde(rename_all = "kebab-case")]
2937pub struct LocaleFile {
2938    /// The version of the locale file.
2939    #[serde(rename = "@version")]
2940    pub version: String,
2941    /// Which languages or dialects this data applies to.
2942    #[serde(rename = "@lang")]
2943    pub lang: LocaleCode,
2944    /// Metadata of the locale.
2945    #[serde(skip_serializing_if = "Option::is_none")]
2946    pub info: Option<LocaleInfo>,
2947    /// The terms used in the locale.
2948    #[serde(skip_serializing_if = "Option::is_none")]
2949    pub terms: Option<Terms>,
2950    /// How to format dates in the locale file.
2951    #[serde(default)]
2952    pub date: Vec<Date>,
2953    /// Style options for the locale.
2954    #[serde(skip_serializing_if = "Option::is_none")]
2955    pub style_options: Option<LocaleOptions>,
2956}
2957
2958impl LocaleFile {
2959    /// Create a locale from an XML string.
2960    pub fn from_xml(xml: &str) -> XmlResult<Self> {
2961        let locale: Self = quick_xml::de::from_str(xml)?;
2962        Ok(locale)
2963    }
2964
2965    /// Write the locale to an XML string.
2966    pub fn to_xml(&self) -> XmlResult<String> {
2967        let mut buf = String::new();
2968        let ser = quick_xml::se::Serializer::with_root(&mut buf, Some("style"))?;
2969        self.serialize(ser)?;
2970        Ok(buf)
2971    }
2972}
2973
2974/// Supplemental localization data in a citation style.
2975#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
2976#[serde(rename_all = "kebab-case")]
2977pub struct Locale {
2978    /// Which languages or dialects this data applies to. Must be `Some` if this
2979    /// appears in a locale file.
2980    #[serde(rename = "@lang")]
2981    #[serde(skip_serializing_if = "Option::is_none")]
2982    pub lang: Option<LocaleCode>,
2983    /// Metadata of the locale.
2984    #[serde(skip_serializing_if = "Option::is_none")]
2985    pub info: Option<LocaleInfo>,
2986    /// The terms used in the locale.
2987    #[serde(skip_serializing_if = "Option::is_none")]
2988    pub terms: Option<Terms>,
2989    /// How to format dates in the locale file.
2990    #[serde(default)]
2991    pub date: Vec<Date>,
2992    /// Style options for the locale.
2993    #[serde(skip_serializing_if = "Option::is_none")]
2994    pub style_options: Option<LocaleOptions>,
2995}
2996
2997impl Locale {
2998    /// Get a term translation.
2999    pub fn term(&self, term: Term, form: TermForm) -> Option<&LocalizedTerm> {
3000        self.terms.as_ref().and_then(|terms| {
3001            terms
3002                .terms
3003                .iter()
3004                .find(|t| t.name.is_lexically_same(term) && t.form == form)
3005        })
3006    }
3007
3008    /// Retrieve a struct for ordinal term lookups if this locale contains any
3009    /// ordinal terms.
3010    pub fn ordinals(&self) -> Option<OrdinalLookup<'_>> {
3011        self.terms.as_ref().and_then(|terms| {
3012            terms.terms.iter().any(|t| t.name.is_ordinal()).then(|| {
3013                OrdinalLookup::new(terms.terms.iter().filter(|t| t.name.is_ordinal()))
3014            })
3015        })
3016    }
3017}
3018
3019/// Get the right forms of ordinal terms for numbers.
3020pub struct OrdinalLookup<'a> {
3021    terms: Vec<&'a LocalizedTerm>,
3022    legacy_behavior: bool,
3023}
3024
3025impl<'a> OrdinalLookup<'a> {
3026    fn new(ordinal_terms: impl Iterator<Item = &'a LocalizedTerm>) -> Self {
3027        let terms = ordinal_terms.collect::<Vec<_>>();
3028        let mut legacy_behavior = false;
3029        // Must not define "OtherTerm::Ordinal"
3030        let defines_ordinal =
3031            terms.iter().any(|t| t.name == Term::Other(OtherTerm::Ordinal));
3032
3033        if !defines_ordinal {
3034            // Contains OtherTerm::OrdinalN(1) - OtherTerm::OrdinalN(4)
3035            legacy_behavior = (1..=4).all(|n| {
3036                terms.iter().any(|t| t.name == Term::Other(OtherTerm::OrdinalN(n)))
3037            })
3038        }
3039
3040        Self { terms, legacy_behavior }
3041    }
3042
3043    /// Create an empty lookup that will never return matches.
3044    pub const fn empty() -> Self {
3045        Self { terms: Vec::new(), legacy_behavior: false }
3046    }
3047
3048    /// Look up a short ordinal for a number.
3049    pub fn lookup(&self, n: i32, gender: Option<GrammarGender>) -> Option<&'a str> {
3050        let mut best_match: Option<&'a LocalizedTerm> = None;
3051
3052        // Prefer match with o > 9 and the smallest difference to n
3053        let mut change_match = |other_match: &'a LocalizedTerm| {
3054            let Some(current) = best_match else {
3055                best_match = Some(other_match);
3056                return;
3057            };
3058
3059            // Extract the number from the term name.
3060            let Term::Other(OtherTerm::OrdinalN(other_n)) = other_match.name else {
3061                return;
3062            };
3063
3064            let Term::Other(OtherTerm::OrdinalN(curr_n)) = current.name else {
3065                best_match = Some(other_match);
3066                return;
3067            };
3068
3069            best_match = Some(if other_n >= 10 && curr_n < 10 {
3070                other_match
3071            } else if other_n < 10 && curr_n >= 10 {
3072                current
3073            } else {
3074                // Both matches are either < 10 or >= 10.
3075                // Check the gender form.
3076                if gender == current.gender && gender != other_match.gender {
3077                    current
3078                } else if gender != current.gender && gender == other_match.gender {
3079                    other_match
3080                } else {
3081                    // Choose the smallest difference.
3082                    let diff_other = (n - other_n as i32).abs();
3083                    let diff_curr = (n - curr_n as i32).abs();
3084
3085                    if diff_other <= diff_curr {
3086                        other_match
3087                    } else {
3088                        current
3089                    }
3090                }
3091            })
3092        };
3093
3094        for term in self.terms.iter().copied() {
3095            let Term::Other(term_name) = term.name else { continue };
3096
3097            let hit = match term_name {
3098                OtherTerm::Ordinal => true,
3099                OtherTerm::OrdinalN(o) if self.legacy_behavior => {
3100                    let class = match (n, n % 10) {
3101                        (11..=13, _) => 4,
3102                        (_, v @ 1..=3) => v as u8,
3103                        _ => 4,
3104                    };
3105                    o == class
3106                }
3107                OtherTerm::OrdinalN(o @ 0..=9) => match term.match_ {
3108                    Some(OrdinalMatch::LastDigit) | None => n % 10 == o as i32,
3109                    Some(OrdinalMatch::LastTwoDigits) => n % 100 == o as i32,
3110                    Some(OrdinalMatch::WholeNumber) => n == o as i32,
3111                },
3112                OtherTerm::OrdinalN(o @ 10..=99) => match term.match_ {
3113                    Some(OrdinalMatch::LastTwoDigits) | None => n % 100 == o as i32,
3114                    Some(OrdinalMatch::WholeNumber) => n == o as i32,
3115                    _ => false,
3116                },
3117                _ => false,
3118            };
3119
3120            if hit {
3121                change_match(term);
3122            }
3123        }
3124
3125        best_match.and_then(|t| t.single().or_else(|| t.multiple()))
3126    }
3127
3128    /// Look up a long ordinal for a number. Does not include fallback to
3129    /// regular ordinals.
3130    pub fn lookup_long(&self, n: i32) -> Option<&'a str> {
3131        self.terms
3132            .iter()
3133            .find(|t| {
3134                let Term::Other(OtherTerm::LongOrdinal(o)) = t.name else { return false };
3135                if n > 0 && n <= 10 {
3136                    n == o as i32
3137                } else {
3138                    match t.match_ {
3139                        Some(OrdinalMatch::LastTwoDigits) | None => n % 100 == o as i32,
3140                        Some(OrdinalMatch::WholeNumber) => n == o as i32,
3141                        _ => false,
3142                    }
3143                }
3144            })
3145            .and_then(|t| t.single().or_else(|| t.multiple()))
3146    }
3147}
3148
3149impl From<LocaleFile> for Locale {
3150    fn from(file: LocaleFile) -> Self {
3151        Self {
3152            lang: Some(file.lang),
3153            info: file.info,
3154            terms: file.terms,
3155            date: file.date,
3156            style_options: file.style_options,
3157        }
3158    }
3159}
3160
3161impl TryFrom<Locale> for LocaleFile {
3162    type Error = ();
3163
3164    fn try_from(value: Locale) -> Result<Self, Self::Error> {
3165        if value.lang.is_some() {
3166            Ok(Self {
3167                version: "1.0".to_string(),
3168                lang: value.lang.unwrap(),
3169                info: value.info,
3170                terms: value.terms,
3171                date: value.date,
3172                style_options: value.style_options,
3173            })
3174        } else {
3175            Err(())
3176        }
3177    }
3178}
3179
3180/// Metadata of a locale.
3181#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
3182pub struct LocaleInfo {
3183    /// The translators of the locale.
3184    #[serde(rename = "translator")]
3185    #[serde(default)]
3186    pub translators: Vec<StyleAttribution>,
3187    /// The license under which the locale is published.
3188    #[serde(skip_serializing_if = "Option::is_none")]
3189    pub rights: Option<License>,
3190    /// When the locale was last updated.
3191    #[serde(skip_serializing_if = "Option::is_none")]
3192    pub updated: Option<Timestamp>,
3193}
3194
3195/// Term localization container.
3196#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
3197pub struct Terms {
3198    /// The terms.
3199    #[serde(rename = "term")]
3200    pub terms: Vec<LocalizedTerm>,
3201}
3202
3203/// A localized term.
3204#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
3205pub struct LocalizedTerm {
3206    /// The term key.
3207    #[serde(rename = "@name")]
3208    pub name: Term,
3209    /// The localization.
3210    #[serde(rename = "$text")]
3211    #[serde(skip_serializing_if = "Option::is_none")]
3212    localization: Option<String>,
3213    /// The singular variant.
3214    #[serde(skip_serializing_if = "Option::is_none")]
3215    single: Option<String>,
3216    /// The plural variant.
3217    #[serde(skip_serializing_if = "Option::is_none")]
3218    multiple: Option<String>,
3219    /// The variant of this term translation.
3220    #[serde(rename = "@form", default)]
3221    pub form: TermForm,
3222    /// Specify the when this ordinal term is used.
3223    #[serde(rename = "@match")]
3224    #[serde(skip_serializing_if = "Option::is_none")]
3225    pub match_: Option<OrdinalMatch>,
3226    /// Specify for which grammatical gender this term has to get corresponding ordinals
3227    #[serde(rename = "@gender")]
3228    #[serde(skip_serializing_if = "Option::is_none")]
3229    pub gender: Option<GrammarGender>,
3230    /// Specify which grammatical gender this ordinal term matches
3231    #[serde(rename = "@gender-form")]
3232    #[serde(skip_serializing_if = "Option::is_none")]
3233    pub gender_form: Option<GrammarGender>,
3234}
3235
3236impl LocalizedTerm {
3237    /// Get the singular variant of this term translation. Shall be defined for
3238    /// valid CSL files.
3239    pub fn single(&self) -> Option<&str> {
3240        self.single.as_deref().or(self.localization.as_deref())
3241    }
3242
3243    /// Get the plural variant of this term translation. Shall be defined for
3244    /// valid CSL files.
3245    pub fn multiple(&self) -> Option<&str> {
3246        self.multiple.as_deref().or(self.localization.as_deref())
3247    }
3248}
3249
3250/// The variant of a term translation.
3251#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
3252#[serde(rename_all = "kebab-case")]
3253pub enum TermForm {
3254    /// The default variant.
3255    #[default]
3256    Long,
3257    /// The short noun variant.
3258    Short,
3259    /// The related verb.
3260    Verb,
3261    /// The related verb (short form).
3262    VerbShort,
3263    /// The symbol variant.
3264    Symbol,
3265}
3266
3267impl TermForm {
3268    /// Which form is the next fallback if this form is not available.
3269    pub const fn fallback(self) -> Option<Self> {
3270        match self {
3271            Self::Long => None,
3272            Self::Short => Some(Self::Long),
3273            Self::Verb => Some(Self::Long),
3274            Self::VerbShort => Some(Self::Verb),
3275            Self::Symbol => Some(Self::Short),
3276        }
3277    }
3278}
3279
3280/// Specify when which ordinal term is used.
3281#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
3282#[serde(rename_all = "kebab-case")]
3283pub enum OrdinalMatch {
3284    /// Match the last digit for ordinal terms between zero and nine and the
3285    /// last two otherwise.
3286    #[default]
3287    LastDigit,
3288    /// Always match on the last two non-zero digits.
3289    LastTwoDigits,
3290    /// Match on the exact number.
3291    WholeNumber,
3292}
3293
3294/// A grammatical gender. Use `None` for neutral.
3295#[allow(missing_docs)]
3296#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
3297#[serde(rename_all = "kebab-case")]
3298pub enum GrammarGender {
3299    Feminine,
3300    Masculine,
3301}
3302
3303/// Options for the locale.
3304#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
3305pub struct LocaleOptions {
3306    /// Only use ordinals for the first day in a month.
3307    ///
3308    /// Default: `false`
3309    #[serde(
3310        rename = "@limit-day-ordinals-to-day-1",
3311        deserialize_with = "deserialize_bool_option",
3312        default
3313    )]
3314    #[serde(skip_serializing_if = "Option::is_none")]
3315    pub limit_day_ordinals_to_day_1: Option<bool>,
3316    /// Whether to place punctuation inside of quotation marks.
3317    ///
3318    /// Default: `false`
3319    #[serde(
3320        rename = "@punctuation-in-quote",
3321        deserialize_with = "deserialize_bool_option",
3322        default
3323    )]
3324    #[serde(skip_serializing_if = "Option::is_none")]
3325    pub punctuation_in_quote: Option<bool>,
3326}
3327
3328/// Formatting properties.
3329#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
3330pub struct Formatting {
3331    /// Set the font style.
3332    #[serde(rename = "@font-style")]
3333    #[serde(skip_serializing_if = "Option::is_none")]
3334    pub font_style: Option<FontStyle>,
3335    /// Choose normal or small caps.
3336    #[serde(rename = "@font-variant")]
3337    #[serde(skip_serializing_if = "Option::is_none")]
3338    pub font_variant: Option<FontVariant>,
3339    /// Set the font weight.
3340    #[serde(rename = "@font-weight")]
3341    #[serde(skip_serializing_if = "Option::is_none")]
3342    pub font_weight: Option<FontWeight>,
3343    /// Choose underlining.
3344    #[serde(rename = "@text-decoration")]
3345    #[serde(skip_serializing_if = "Option::is_none")]
3346    pub text_decoration: Option<TextDecoration>,
3347    /// Choose vertical alignment.
3348    #[serde(rename = "@vertical-align")]
3349    #[serde(skip_serializing_if = "Option::is_none")]
3350    pub vertical_align: Option<VerticalAlign>,
3351}
3352
3353impl Formatting {
3354    /// Check if this formatting is empty.
3355    pub fn is_empty(&self) -> bool {
3356        self.font_style.is_none()
3357            && self.font_variant.is_none()
3358            && self.font_weight.is_none()
3359            && self.text_decoration.is_none()
3360            && self.vertical_align.is_none()
3361    }
3362
3363    /// Merge with a base formatting.
3364    pub fn apply(self, base: Self) -> Self {
3365        Self {
3366            font_style: self.font_style.or(base.font_style),
3367            font_variant: self.font_variant.or(base.font_variant),
3368            font_weight: self.font_weight.or(base.font_weight),
3369            text_decoration: self.text_decoration.or(base.text_decoration),
3370            vertical_align: self.vertical_align.or(base.vertical_align),
3371        }
3372    }
3373}
3374
3375/// Font style.
3376#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
3377#[serde(rename_all = "lowercase")]
3378pub enum FontStyle {
3379    /// Normal font style.
3380    #[default]
3381    Normal,
3382    /// Italic font style.
3383    Italic,
3384}
3385
3386/// Font variant.
3387#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
3388#[serde(rename_all = "kebab-case")]
3389pub enum FontVariant {
3390    /// Normal font variant.
3391    #[default]
3392    Normal,
3393    /// Small caps font variant.
3394    SmallCaps,
3395}
3396
3397/// Font weight.
3398#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
3399#[serde(rename_all = "lowercase")]
3400pub enum FontWeight {
3401    /// Normal font weight.
3402    #[default]
3403    Normal,
3404    /// Bold font weight.
3405    Bold,
3406    /// Light font weight.
3407    Light,
3408}
3409
3410/// Text decoration.
3411#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
3412#[serde(rename_all = "lowercase")]
3413pub enum TextDecoration {
3414    /// No text decoration.
3415    #[default]
3416    None,
3417    /// Underline text decoration.
3418    Underline,
3419}
3420
3421/// Vertical alignment.
3422#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
3423#[serde(rename_all = "lowercase")]
3424pub enum VerticalAlign {
3425    /// No vertical alignment.
3426    #[default]
3427    #[serde(rename = "")]
3428    None,
3429    /// Align on the baseline.
3430    Baseline,
3431    /// Superscript vertical alignment.
3432    Sup,
3433    /// Subscript vertical alignment.
3434    Sub,
3435}
3436
3437/// Prefixes and suffixes.
3438#[derive(Debug, Default, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
3439pub struct Affixes {
3440    /// The prefix.
3441    #[serde(rename = "@prefix")]
3442    #[serde(skip_serializing_if = "Option::is_none")]
3443    pub prefix: Option<String>,
3444    /// The suffix.
3445    #[serde(rename = "@suffix")]
3446    #[serde(skip_serializing_if = "Option::is_none")]
3447    pub suffix: Option<String>,
3448}
3449
3450/// On which layout level to display the citation.
3451#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
3452#[serde(rename_all = "kebab-case")]
3453pub enum Display {
3454    /// Block stretching from margin to margin.
3455    Block,
3456    /// Put in the left margin.
3457    LeftMargin,
3458    /// Align on page after `LeftMargin`.
3459    RightInline,
3460    /// `Block` and indented.
3461    Indent,
3462}
3463
3464/// How to format text.
3465#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
3466#[serde(rename_all = "kebab-case")]
3467pub enum TextCase {
3468    /// lowecase.
3469    Lowercase,
3470    /// UPPERCASE.
3471    Uppercase,
3472    /// Capitalize the first word.
3473    CapitalizeFirst,
3474    /// Capitalize All Words.
3475    CapitalizeAll,
3476    /// Sentence case. *Deprecated*.
3477    #[serde(rename = "sentence")]
3478    SentenceCase,
3479    /// Title case. Only applies to English.
3480    #[serde(rename = "title")]
3481    TitleCase,
3482}
3483
3484impl TextCase {
3485    /// Check whether this case can be applied to languages other than English.
3486    pub fn is_language_independent(self) -> bool {
3487        match self {
3488            Self::Lowercase
3489            | Self::Uppercase
3490            | Self::CapitalizeFirst
3491            | Self::CapitalizeAll => true,
3492            Self::SentenceCase | Self::TitleCase => false,
3493        }
3494    }
3495}
3496
3497#[cfg(test)]
3498mod test {
3499    use super::*;
3500    use serde::de::DeserializeOwned;
3501    use std::{error::Error, fs};
3502
3503    fn folder<F>(
3504        files: &'static str,
3505        extension: &'static str,
3506        kind: &'static str,
3507        mut check: F,
3508    ) where
3509        F: FnMut(&str) -> Option<Box<dyn Error>>,
3510    {
3511        let mut failures = 0;
3512        let mut tests = 0;
3513
3514        // Read each `.csl` file in the `tests` directory.
3515        for entry in fs::read_dir(files).unwrap() {
3516            let entry = entry.unwrap();
3517            let path = entry.path();
3518            if path.extension().map(|os| os.to_str().unwrap()) != Some(extension)
3519                || !entry.file_type().unwrap().is_file()
3520            {
3521                continue;
3522            }
3523
3524            tests += 1;
3525
3526            let source = fs::read_to_string(&path).unwrap();
3527            let result = check(&source);
3528            if let Some(err) = result {
3529                failures += 1;
3530                println!("āŒ {:?} failed: \n\n{:#?}", &path, &err);
3531            }
3532        }
3533
3534        if failures == 0 {
3535            print!("\nšŸŽ‰")
3536        } else {
3537            print!("\n😢")
3538        }
3539
3540        println!(
3541            " {} out of {} {} files parsed successfully",
3542            tests - failures,
3543            tests,
3544            kind
3545        );
3546
3547        if failures > 0 {
3548            panic!("{} tests failed", failures);
3549        }
3550    }
3551
3552    fn check_style(csl_files: &'static str, kind: &'static str) {
3553        folder(csl_files, "csl", kind, |source| {
3554            let de = &mut deserializer(source);
3555            let result: Result<RawStyle, _> = serde_path_to_error::deserialize(de);
3556            match result {
3557                Ok(_) => None,
3558                Err(err) => Some(Box::new(err)),
3559            }
3560        })
3561    }
3562
3563    fn check_locale(locale_files: &'static str) {
3564        folder(locale_files, "xml", "Locale", |source| {
3565            let de = &mut deserializer(source);
3566            let result: Result<LocaleFile, _> = serde_path_to_error::deserialize(de);
3567            match result {
3568                Ok(_) => None,
3569                Err(err) => Some(Box::new(err)),
3570            }
3571        })
3572    }
3573
3574    #[track_caller]
3575    fn to_cbor<T: Serialize>(style: &T) -> Vec<u8> {
3576        let mut buf = Vec::new();
3577        ciborium::ser::into_writer(style, &mut buf).unwrap();
3578        buf
3579    }
3580
3581    #[track_caller]
3582    fn from_cbor<T: DeserializeOwned>(reader: &[u8]) -> T {
3583        ciborium::de::from_reader(reader).unwrap()
3584    }
3585
3586    #[test]
3587    fn test_independent() {
3588        check_style("tests/independent", "independent CSL style");
3589    }
3590
3591    #[test]
3592    fn test_dependent() {
3593        check_style("tests/dependent", "dependent CSL style");
3594    }
3595
3596    #[test]
3597    fn test_locale() {
3598        check_locale("tests/locales");
3599    }
3600
3601    /// Be sure to check out the CSL
3602    /// [styles](https://github.com/citation-style-language/styles) repository
3603    /// into a sibling folder to run this test.
3604    #[test]
3605    fn roundtrip_cbor_all() {
3606        fs::create_dir_all("tests/artifacts/styles").unwrap();
3607        for style_thing in
3608            fs::read_dir("../styles/").expect("please check out the CSL styles repo")
3609        {
3610            let thing = style_thing.unwrap();
3611            if thing.file_type().unwrap().is_dir() {
3612                continue;
3613            }
3614
3615            let path = thing.path();
3616            let extension = path.extension();
3617            if let Some(extension) = extension {
3618                if extension.to_str() != Some("csl") {
3619                    continue;
3620                }
3621            } else {
3622                continue;
3623            }
3624
3625            eprintln!("Testing {}", path.display());
3626            let source = fs::read_to_string(&path).unwrap();
3627            let style = Style::from_xml(&source).unwrap();
3628            let cbor = to_cbor(&style);
3629            fs::write(
3630                format!(
3631                    "tests/artifacts/styles/{}.cbor",
3632                    path.file_stem().unwrap().to_str().unwrap()
3633                ),
3634                &cbor,
3635            )
3636            .unwrap();
3637            let style2 = from_cbor(&cbor);
3638            assert_eq!(style, style2);
3639        }
3640    }
3641
3642    /// Be sure to check out the CSL
3643    /// [locales](https://github.com/citation-style-language/locales) repository
3644    /// into a sibling folder to run this test.
3645    #[test]
3646    fn roundtrip_cbor_all_locales() {
3647        fs::create_dir_all("tests/artifacts/locales").unwrap();
3648        for style_thing in
3649            fs::read_dir("../locales/").expect("please check out the CSL locales repo")
3650        {
3651            let thing = style_thing.unwrap();
3652            if thing.file_type().unwrap().is_dir() {
3653                continue;
3654            }
3655
3656            let path = thing.path();
3657            let extension = path.extension();
3658            if let Some(extension) = extension {
3659                if extension.to_str() != Some("xml")
3660                    || !path
3661                        .file_stem()
3662                        .unwrap()
3663                        .to_str()
3664                        .unwrap()
3665                        .starts_with("locales-")
3666                {
3667                    continue;
3668                }
3669            } else {
3670                continue;
3671            }
3672
3673            eprintln!("Testing {}", path.display());
3674            let source = fs::read_to_string(&path).unwrap();
3675            let locale = LocaleFile::from_xml(&source).unwrap();
3676            let cbor = to_cbor(&locale);
3677            fs::write(
3678                format!(
3679                    "tests/artifacts/locales/{}.cbor",
3680                    path.file_stem().unwrap().to_str().unwrap()
3681                ),
3682                &cbor,
3683            )
3684            .unwrap();
3685            let locale2 = from_cbor(&cbor);
3686            assert_eq!(locale, locale2);
3687        }
3688    }
3689
3690    #[test]
3691    fn page_range() {
3692        fn run(format: PageRangeFormat, start: &str, end: &str) -> String {
3693            let mut buf = String::new();
3694            format.format(&mut buf, start, end, None).unwrap();
3695            buf
3696        }
3697
3698        let c15 = PageRangeFormat::Chicago15;
3699        let c16 = PageRangeFormat::Chicago16;
3700        let exp = PageRangeFormat::Expanded;
3701        let min = PageRangeFormat::Minimal;
3702        let mi2 = PageRangeFormat::MinimalTwo;
3703
3704        // https://docs.citationstyles.org/en/stable/specification.html#appendix-v-page-range-formats
3705
3706        assert_eq!("3–10", run(c15, "3", "10"));
3707        assert_eq!("71–72", run(c15, "71", "72"));
3708        assert_eq!("100–104", run(c15, "100", "4"));
3709        assert_eq!("600–613", run(c15, "600", "613"));
3710        assert_eq!("1100–1123", run(c15, "1100", "1123"));
3711        assert_eq!("107–8", run(c15, "107", "108"));
3712        assert_eq!("505–17", run(c15, "505", "517"));
3713        assert_eq!("1002–6", run(c15, "1002", "1006"));
3714        assert_eq!("321–25", run(c15, "321", "325"));
3715        assert_eq!("415–532", run(c15, "415", "532"));
3716        assert_eq!("11564–68", run(c15, "11564", "11568"));
3717        assert_eq!("13792–803", run(c15, "13792", "13803"));
3718        assert_eq!("1496–1504", run(c15, "1496", "1504"));
3719        assert_eq!("2787–2816", run(c15, "2787", "2816"));
3720        assert_eq!("101–8", run(c15, "101", "108"));
3721
3722        assert_eq!("3–10", run(c16, "3", "10"));
3723        assert_eq!("71–72", run(c16, "71", "72"));
3724        assert_eq!("92–113", run(c16, "92", "113"));
3725        assert_eq!("100–104", run(c16, "100", "4"));
3726        assert_eq!("600–613", run(c16, "600", "613"));
3727        assert_eq!("1100–1123", run(c16, "1100", "1123"));
3728        assert_eq!("107–8", run(c16, "107", "108"));
3729        assert_eq!("505–17", run(c16, "505", "517"));
3730        assert_eq!("1002–6", run(c16, "1002", "1006"));
3731        assert_eq!("321–25", run(c16, "321", "325"));
3732        assert_eq!("415–532", run(c16, "415", "532"));
3733        assert_eq!("1087–89", run(c16, "1087", "1089"));
3734        assert_eq!("1496–500", run(c16, "1496", "1500"));
3735        assert_eq!("11564–68", run(c16, "11564", "11568"));
3736        assert_eq!("13792–803", run(c16, "13792", "13803"));
3737        assert_eq!("12991–3001", run(c16, "12991", "13001"));
3738        assert_eq!("12991–123001", run(c16, "12991", "123001"));
3739
3740        assert_eq!("42–45", run(exp, "42", "45"));
3741        assert_eq!("321–328", run(exp, "321", "328"));
3742        assert_eq!("2787–2816", run(exp, "2787", "2816"));
3743
3744        assert_eq!("42–5", run(min, "42", "45"));
3745        assert_eq!("321–8", run(min, "321", "328"));
3746        assert_eq!("2787–816", run(min, "2787", "2816"));
3747
3748        assert_eq!("7–8", run(mi2, "7", "8"));
3749        assert_eq!("42–45", run(mi2, "42", "45"));
3750        assert_eq!("321–28", run(mi2, "321", "328"));
3751        assert_eq!("2787–816", run(mi2, "2787", "2816"));
3752    }
3753
3754    /// Tests the bug from PR typst/hayagriva#155
3755    #[test]
3756    fn test_bug_hayagriva_115() {
3757        fn run(format: PageRangeFormat, start: &str, end: &str) -> String {
3758            let mut buf = String::new();
3759            format.format(&mut buf, start, end, None).unwrap();
3760            buf
3761        }
3762        let c16 = PageRangeFormat::Chicago16;
3763
3764        assert_eq!("12991–123001", run(c16, "12991", "123001"));
3765    }
3766
3767    #[test]
3768    fn page_range_prefix() {
3769        fn run(format: PageRangeFormat, start: &str, end: &str) -> String {
3770            let mut buf = String::new();
3771            format.format(&mut buf, start, end, None).unwrap();
3772            buf
3773        }
3774
3775        let c15 = PageRangeFormat::Chicago15;
3776        let exp = PageRangeFormat::Expanded;
3777        let min = PageRangeFormat::Minimal;
3778
3779        assert_eq!("8n11564–68", run(c15, "8n11564", "8n1568"));
3780        assert_eq!("n11564–68", run(c15, "n11564", "n1568"));
3781        assert_eq!("n11564–1568", run(c15, "n11564", "1568"));
3782
3783        assert_eq!("N110–5", run(exp, "N110 ", " 5"));
3784        assert_eq!("N110–N115", run(exp, "N110 ", " N5"));
3785        assert_eq!("110–N6", run(exp, "110 ", " N6"));
3786        assert_eq!("N110–P5", run(exp, "N110 ", " P5"));
3787        assert_eq!("123N110–N5", run(exp, "123N110 ", " N5"));
3788        assert_eq!("456K200–99", run(exp, "456K200 ", " 99"));
3789        assert_eq!("000c23–22", run(exp, "000c23 ", " 22"));
3790
3791        assert_eq!("n11564–8", run(min, "n11564 ", " n1568"));
3792        assert_eq!("n11564–1568", run(min, "n11564 ", " 1568"));
3793    }
3794}