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}