Skip to main content

mos_csl/
style.rs

1//! The typed CSL style AST produced by [`parse_style`](crate::parse_style).
2//!
3//! This models the structure of a CSL 1.0.2 style; the `<style>` root, its
4//! `<info>`, `<citation>`, `<bibliography>`, and `<macro>` children, and the
5//! rendering elements ([`Element`]) with their common attributes. It is a
6//! faithful *structural* model, not a processor: there is no evaluation of a
7//! style against data here. Rendering-critical options are retained on typed
8//! option structs so a future processor can interpret them. Attributes outside
9//! the modelled set are ignored; unknown rendering elements are a parse error.
10
11use std::collections::BTreeMap;
12
13/// A parsed CSL style (the `<style>` root element).
14#[derive(Clone, Debug, PartialEq, Eq)]
15pub struct Style {
16    /// Whether citations are in-text or notes (`class` attribute).
17    pub class: StyleClass,
18    /// The declared CSL version (`version` attribute, e.g. `"1.0"`).
19    pub version: String,
20    /// An optional overriding style locale (`default-locale`).
21    pub default_locale: Option<String>,
22    /// Global style options retained for a future processor.
23    pub options: StyleOptions,
24    /// Style metadata from `<info>`.
25    pub info: Info,
26    /// The `<citation>` element, if present.
27    pub citation: Option<Citation>,
28    /// The `<bibliography>` element, if present.
29    pub bibliography: Option<Bibliography>,
30    /// `<macro>` definitions keyed by name, in sorted order.
31    pub macros: BTreeMap<String, Vec<Element>>,
32    /// In-style `<locale>` blocks retained as raw XML for future locale support.
33    pub locales: Vec<LocaleBlock>,
34}
35
36/// An in-style `<locale>` block retained but not interpreted.
37#[derive(Clone, Debug, PartialEq, Eq)]
38pub struct LocaleBlock {
39    pub xml: String,
40}
41
42/// Global `<style>` rendering options retained but not evaluated.
43#[derive(Clone, Debug, Default, PartialEq, Eq)]
44pub struct StyleOptions {
45    pub page_range_format: Option<String>,
46    pub demote_non_dropping_particle: Option<String>,
47    pub initialize_with_hyphen: Option<String>,
48    /// Inheritable name options set as `<style>`-level defaults.
49    pub names: InheritableNameOptions,
50}
51
52/// Inheritable name options, valid on `<style>`, `<citation>`, `<bibliography>`,
53/// and `<name>`. Retained but not evaluated; a processor applies them as the
54/// cascading defaults a `<name>` element inherits.
55#[derive(Clone, Debug, Default, PartialEq, Eq)]
56pub struct InheritableNameOptions {
57    pub et_al_min: Option<String>,
58    pub et_al_use_first: Option<String>,
59    pub et_al_subsequent_min: Option<String>,
60    pub et_al_subsequent_use_first: Option<String>,
61    pub et_al_use_last: Option<String>,
62    /// `and="text"|"symbol"`.
63    pub and: Option<String>,
64    pub delimiter_precedes_et_al: Option<String>,
65    pub delimiter_precedes_last: Option<String>,
66    pub initialize: Option<String>,
67    pub initialize_with: Option<String>,
68    pub name_as_sort_order: Option<String>,
69    pub sort_separator: Option<String>,
70}
71
72/// The `class` of a CSL style.
73#[derive(Clone, Copy, Debug, PartialEq, Eq)]
74pub enum StyleClass {
75    /// In-text citations (`class="in-text"`).
76    InText,
77    /// Note (foot/endnote) citations (`class="note"`).
78    Note,
79}
80
81/// Style metadata (`<info>`). Only the commonly-needed fields are modelled.
82#[derive(Clone, Debug, Default, PartialEq, Eq)]
83pub struct Info {
84    /// `<id>`; the stable style identifier.
85    pub id: Option<String>,
86    /// `<title>`; the human-facing style name.
87    pub title: Option<String>,
88    /// `<link>` entries, including dependent-style `rel="independent-parent"`.
89    pub links: Vec<InfoLink>,
90    /// `<category>` entries describing citation format or field.
91    pub categories: Vec<InfoCategory>,
92    /// `<author>` entries.
93    pub authors: Vec<InfoContributor>,
94    /// `<contributor>` entries.
95    pub contributors: Vec<InfoContributor>,
96    /// `<updated>` timestamp text.
97    pub updated: Option<String>,
98    /// `<issn>` values.
99    pub issn: Vec<String>,
100}
101
102/// An `<info><link .../>` entry.
103#[derive(Clone, Debug, Default, PartialEq, Eq)]
104pub struct InfoLink {
105    pub rel: Option<String>,
106    pub href: Option<String>,
107    pub media_type: Option<String>,
108}
109
110/// An `<info><category .../>` entry.
111#[derive(Clone, Debug, Default, PartialEq, Eq)]
112pub struct InfoCategory {
113    pub citation_format: Option<String>,
114    pub field: Option<String>,
115}
116
117/// An `<info><author>` or `<info><contributor>` entry.
118#[derive(Clone, Debug, Default, PartialEq, Eq)]
119pub struct InfoContributor {
120    pub name: Option<String>,
121    pub uri: Option<String>,
122    pub email: Option<String>,
123}
124
125/// The `<citation>` element: how cites are formatted.
126#[derive(Clone, Debug, PartialEq, Eq)]
127pub struct Citation {
128    /// The required `<layout>`.
129    pub layout: Layout,
130    /// `<sort>` keys, in priority order (empty when absent).
131    pub sort: Vec<SortKey>,
132    /// Citation-specific rendering options retained for a future processor.
133    pub options: CitationOptions,
134}
135
136/// `<citation>` options retained but not evaluated.
137#[derive(Clone, Debug, Default, PartialEq, Eq)]
138pub struct CitationOptions {
139    pub collapse: Option<String>,
140    pub cite_group_delimiter: Option<String>,
141    pub year_suffix_delimiter: Option<String>,
142    pub after_collapse_delimiter: Option<String>,
143    pub disambiguate_add_names: Option<String>,
144    pub disambiguate_add_givenname: Option<String>,
145    pub disambiguate_add_year_suffix: Option<String>,
146    pub givenname_disambiguation_rule: Option<String>,
147    pub near_note_distance: Option<String>,
148    /// Inheritable name options set as `<citation>`-level defaults.
149    pub names: InheritableNameOptions,
150}
151
152/// The `<bibliography>` element: how bibliography entries are formatted.
153///
154/// This is the CSL *style* element, distinct from a bibliographic database
155/// (`mos_bib::Bibliography`).
156#[derive(Clone, Debug, PartialEq, Eq)]
157pub struct Bibliography {
158    /// The required `<layout>`.
159    pub layout: Layout,
160    /// `<sort>` keys, in priority order (empty when absent).
161    pub sort: Vec<SortKey>,
162    /// Bibliography-specific rendering options retained for a future processor.
163    pub options: BibliographyOptions,
164}
165
166/// `<bibliography>` options retained but not evaluated.
167#[derive(Clone, Debug, Default, PartialEq, Eq)]
168pub struct BibliographyOptions {
169    pub hanging_indent: Option<String>,
170    pub second_field_align: Option<String>,
171    pub line_spacing: Option<String>,
172    pub entry_spacing: Option<String>,
173    pub subsequent_author_substitute: Option<String>,
174    pub subsequent_author_substitute_rule: Option<String>,
175    /// Inheritable name options set as `<bibliography>`-level defaults.
176    pub names: InheritableNameOptions,
177}
178
179/// A `<layout>`: an ordered list of rendering elements plus common attributes.
180#[derive(Clone, Debug, Default, PartialEq, Eq)]
181pub struct Layout {
182    pub elements: Vec<Element>,
183    pub common: Common,
184}
185
186/// One `<key>` in a `<sort>`.
187#[derive(Clone, Debug, PartialEq, Eq)]
188pub struct SortKey {
189    pub target: SortTarget,
190    /// `true` for `sort="descending"` (default is ascending).
191    pub descending: bool,
192    /// Name-rendering sort-key options retained for a future processor.
193    pub options: SortKeyOptions,
194}
195
196/// `<key>` options retained but not evaluated.
197#[derive(Clone, Debug, Default, PartialEq, Eq)]
198pub struct SortKeyOptions {
199    pub names_min: Option<String>,
200    pub names_use_first: Option<String>,
201    pub names_use_last: Option<String>,
202}
203
204/// What a [`SortKey`] sorts on.
205#[derive(Clone, Debug, PartialEq, Eq)]
206pub enum SortTarget {
207    Variable(String),
208    Macro(String),
209}
210
211/// Common rendering attributes shared across most CSL elements (affixes,
212/// formatting, and `delimiter`). Unmodelled attributes are ignored by the
213/// parser.
214#[derive(Clone, Debug, Default, PartialEq, Eq)]
215pub struct Common {
216    pub prefix: Option<String>,
217    pub suffix: Option<String>,
218    pub delimiter: Option<String>,
219    pub font_style: Option<String>,
220    pub font_variant: Option<String>,
221    pub font_weight: Option<String>,
222    pub text_decoration: Option<String>,
223    pub vertical_align: Option<String>,
224    pub text_case: Option<String>,
225    pub display: Option<String>,
226}
227
228/// A CSL rendering element.
229#[derive(Clone, Debug, PartialEq, Eq)]
230pub enum Element {
231    Text(Text),
232    Number(Number),
233    Date(DateElement),
234    /// Boxed: `<names>` is by far the largest element (it nests `<name>`,
235    /// `<et-al>`, `<label>`, and a substitute list), so it is boxed to keep
236    /// [`Element`] small.
237    Names(Box<Names>),
238    Label(Label),
239    Group(Group),
240    Choose(Choose),
241}
242
243/// `<text>`: renders a variable, macro, term, or literal value.
244#[derive(Clone, Debug, PartialEq, Eq)]
245pub struct Text {
246    pub source: TextSource,
247    pub quotes: bool,
248    pub strip_periods: bool,
249    pub common: Common,
250}
251
252/// What a [`Text`] element renders.
253#[derive(Clone, Debug, PartialEq, Eq)]
254pub enum TextSource {
255    /// `variable="..."` (with optional `form`).
256    Variable { name: String, form: Option<String> },
257    /// `macro="..."`.
258    Macro(String),
259    /// `term="..."` (with optional `form` and `plural`).
260    Term {
261        name: String,
262        form: Option<String>,
263        plural: bool,
264    },
265    /// `value="..."`: a literal string.
266    Value(String),
267}
268
269/// `<number>`: renders a number variable.
270#[derive(Clone, Debug, PartialEq, Eq)]
271pub struct Number {
272    pub variable: String,
273    pub form: Option<String>,
274    pub common: Common,
275}
276
277/// `<date>`: renders a date variable, localized or with explicit parts.
278#[derive(Clone, Debug, PartialEq, Eq)]
279pub struct DateElement {
280    pub variable: String,
281    /// `form="numeric"|"text"` for a localized date, or `None`.
282    pub form: Option<String>,
283    /// `date-parts="year-month-day"|"year-month"|"year"`.
284    pub date_parts: Option<String>,
285    pub parts: Vec<DatePart>,
286    pub common: Common,
287}
288
289/// A `<date-part>` child of `<date>`.
290#[derive(Clone, Debug, PartialEq, Eq)]
291pub struct DatePart {
292    /// `name="day"|"month"|"year"`.
293    pub name: String,
294    pub form: Option<String>,
295    pub range_delimiter: Option<String>,
296    /// `strip-periods="true"|"false"` (only meaningful when `name="month"`).
297    pub strip_periods: Option<String>,
298    pub common: Common,
299}
300
301/// `<names>`: renders one or more name variables.
302#[derive(Clone, Debug, PartialEq, Eq)]
303pub struct Names {
304    /// The `variable` attribute, split on whitespace.
305    pub variables: Vec<String>,
306    pub name: Option<NameElement>,
307    pub et_al: Option<EtAl>,
308    pub label: Option<Label>,
309    /// `<substitute>` fallback elements.
310    pub substitute: Vec<Element>,
311    pub common: Common,
312}
313
314/// The `<name>` child of `<names>` (a subset of its many options).
315///
316/// `<name>` carries the same inheritable name options as `<style>`/`<citation>`/
317/// `<bibliography>` (including `and`), retained in [`options`](Self::options).
318#[derive(Clone, Debug, Default, PartialEq, Eq)]
319pub struct NameElement {
320    pub form: Option<String>,
321    /// Name-rendering options retained for a future processor.
322    pub options: InheritableNameOptions,
323    /// `<name-part>` formatting overrides.
324    pub parts: Vec<NamePart>,
325    pub common: Common,
326}
327
328/// A `<name-part>` child of `<name>`.
329#[derive(Clone, Debug, PartialEq, Eq)]
330pub struct NamePart {
331    pub name: Option<String>,
332    pub common: Common,
333}
334
335/// The `<et-al>` child of `<names>`.
336#[derive(Clone, Debug, Default, PartialEq, Eq)]
337pub struct EtAl {
338    /// `term="et-al"|"and others"`.
339    pub term: Option<String>,
340    pub common: Common,
341}
342
343/// `<label>`: renders a term matching a variable.
344#[derive(Clone, Debug, PartialEq, Eq)]
345pub struct Label {
346    /// The `variable` attribute. `None` when nested in `<names>` (it inherits
347    /// the parent's variables).
348    pub variable: Option<String>,
349    pub form: Option<String>,
350    pub plural: Option<String>,
351    pub strip_periods: Option<String>,
352    pub common: Common,
353}
354
355/// `<group>`: a delimited, conditionally-suppressed run of elements.
356#[derive(Clone, Debug, Default, PartialEq, Eq)]
357pub struct Group {
358    pub children: Vec<Element>,
359    pub common: Common,
360}
361
362/// `<choose>`: conditional rendering.
363#[derive(Clone, Debug, Default, PartialEq, Eq)]
364pub struct Choose {
365    /// `<if>` followed by any `<else-if>` branches, in order.
366    pub branches: Vec<Branch>,
367    /// `<else>` elements (empty when absent).
368    pub otherwise: Vec<Element>,
369}
370
371/// One `<if>` / `<else-if>` branch of a [`Choose`].
372#[derive(Clone, Debug, PartialEq, Eq)]
373pub struct Branch {
374    pub conditions: Conditions,
375    pub children: Vec<Element>,
376}
377
378/// The conditions on an `<if>` / `<else-if>`.
379#[derive(Clone, Debug, Default, PartialEq, Eq)]
380pub struct Conditions {
381    pub match_mode: Match,
382    pub kind: Vec<String>,
383    pub variable: Vec<String>,
384    pub is_numeric: Vec<String>,
385    pub is_uncertain_date: Vec<String>,
386    pub locator: Vec<String>,
387    pub position: Vec<String>,
388    pub disambiguate: bool,
389}
390
391/// The `match` attribute on a condition set.
392#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
393pub enum Match {
394    /// All conditions must hold (`match="all"`, the default).
395    #[default]
396    All,
397    /// Any condition may hold (`match="any"`).
398    Any,
399    /// No condition may hold (`match="none"`).
400    None,
401}