Skip to main content

citum_engine/processor/
mod.rs

1/*
2SPDX-License-Identifier: MIT OR Apache-2.0
3SPDX-FileCopyrightText: © 2023-2026 Bruce D'Arcus and Citum contributors
4*/
5
6//! The Citum processor for rendering citations and bibliographies.
7//!
8//! ## Architecture
9//!
10//! `Processor` is intentionally a thin facade over a small set of focused
11//! implementation modules:
12//! - `setup`: construction, configuration resolution, and numbering setup
13//! - `note_context`: note-number normalization and citation position inference
14//! - `citation`: citation rendering orchestration
15//! - `bibliography`: bibliography rendering, grouping, and document-facing helpers
16//!
17//! The processor remains intentionally "dumb": it applies the style as written
18//! without implicit logic. Style-specific behavior (for example, suppressing a
19//! publisher for journals) should be expressed in the style YAML via
20//! `overrides`, not hardcoded here.
21//!
22//! ## CSL 1.0 Compatibility
23//!
24//! The processor implements the CSL 1.0 "variable-once" rule:
25//! > "Substituted variables are suppressed in the rest of the output to
26//! > prevent duplication."
27//!
28//! This is tracked by `TemplateComponentTracker` during template rendering.
29//! Suppressed components do not claim variables; see
30//! `docs/specs/TEMPLATE_RENDERING_SEMANTICS.md`.
31
32mod bibliography;
33mod citation;
34mod note_context;
35mod run_state;
36mod setup;
37
38/// Author/date disambiguation and year-suffix assignment.
39pub mod disambiguation;
40pub mod document;
41pub mod labels;
42/// Matching helpers for substitution and repeated-contributor detection.
43pub mod matching;
44/// Template rendering orchestration and per-component state handling.
45pub mod rendering;
46
47#[cfg(test)]
48#[allow(
49    clippy::unwrap_used,
50    clippy::expect_used,
51    clippy::panic,
52    clippy::indexing_slicing,
53    clippy::todo,
54    clippy::unimplemented,
55    clippy::unreachable,
56    clippy::get_unwrap,
57    reason = "Panicking is acceptable and often desired in tests."
58)]
59mod tests;
60
61use crate::reference::Bibliography;
62use crate::render::ProcEntry;
63use crate::values::ProcHints;
64use citum_schema::Style;
65use citum_schema::locale::Locale;
66use citum_schema::options::Config;
67use indexmap::IndexMap;
68pub use run_state::{FinalizedRun, RunState};
69use std::collections::HashMap;
70
71/// The Citum processor facade.
72///
73/// Takes a style, bibliography, and locale context, then delegates citation
74/// and bibliography work to the processor submodules.
75#[derive(Debug)]
76pub struct Processor {
77    /// The style definition.
78    pub style: Style,
79    /// The bibliography (references keyed by ID).
80    pub bibliography: Bibliography,
81    /// The locale for terms and formatting.
82    pub locale: Locale,
83    /// Default configuration.
84    pub default_config: Config,
85    /// Pre-calculated processing hints.
86    pub hints: HashMap<String, ProcHints>,
87    /// Compound sets keyed by set ID.
88    pub compound_sets: IndexMap<String, Vec<String>>,
89    /// Reverse lookup for set membership by reference ID.
90    pub compound_set_by_ref: HashMap<String, String>,
91    /// Position within a set (0-based) for each reference ID.
92    pub compound_member_index: HashMap<String, usize>,
93    /// Whether to output semantic markup (HTML spans, Djot attributes).
94    /// Defaults to true; set to false to suppress class attributes (e.g. `--no-semantics`).
95    pub show_semantics: bool,
96    /// Whether to annotate semantic HTML wrappers with source template indices.
97    pub inject_ast_indices: bool,
98    /// Document-level abbreviation map for post-render substitution.
99    pub abbreviation_map: Option<crate::api::AbbreviationMap>,
100}
101
102/// Processed output containing citations and bibliography.
103#[derive(Debug, Default)]
104pub struct ProcessedReferences {
105    /// Rendered bibliography entries with metadata.
106    pub bibliography: Vec<ProcEntry>,
107    /// Rendered citations as formatted strings.
108    ///
109    /// None if no citations were processed; Some(vec) otherwise.
110    pub citations: Option<Vec<String>>,
111}
112
113/// Validate optional compound sets against the loaded bibliography.
114///
115/// Validation rules:
116/// - Every member ID must exist in `bibliography`.
117/// - A member ID must not appear more than once in a single set.
118/// - A member ID must not appear across multiple sets.
119///
120/// # Errors
121///
122/// Returns an error when a compound set references an unknown ID or reuses the
123/// same member within or across sets.
124pub fn validate_compound_sets(
125    sets: Option<IndexMap<String, Vec<String>>>,
126    bibliography: &Bibliography,
127) -> Result<Option<IndexMap<String, Vec<String>>>, crate::error::ProcessorError> {
128    let Some(sets) = sets else {
129        return Ok(None);
130    };
131
132    let mut member_owner: HashMap<String, String> = HashMap::new();
133    for (set_id, members) in &sets {
134        let mut seen_in_set: std::collections::HashSet<String> = std::collections::HashSet::new();
135        for member in members {
136            if !seen_in_set.insert(member.clone()) {
137                return Err(crate::error::ProcessorError::CompoundSetValidation(
138                    format!(
139                        "reference '{member}' appears more than once in compound set '{set_id}'"
140                    ),
141                ));
142            }
143            if !bibliography.contains_key(member) {
144                return Err(crate::error::ProcessorError::CompoundSetValidation(
145                    format!("compound set '{set_id}' references unknown id '{member}'"),
146                ));
147            }
148            if let Some(existing) = member_owner.insert(member.clone(), set_id.clone()) {
149                return Err(crate::error::ProcessorError::CompoundSetValidation(
150                    format!(
151                        "reference '{member}' appears in both compound sets '{existing}' and '{set_id}'"
152                    ),
153                ));
154            }
155        }
156    }
157
158    Ok(Some(sets))
159}