Skip to main content

citum_engine/processor/
matching.rs

1/*
2SPDX-License-Identifier: MIT OR Apache-2.0
3SPDX-FileCopyrightText: © 2023-2026 Bruce D'Arcus and Citum contributors
4*/
5
6//! Matching logic for determining if references share primary contributors.
7//!
8//! This module implements contributor matching according to substitution rules,
9//! allowing comparison of references (particularly for "ibid" tracking) based on author,
10//! editor, translator, or title fallback logic.
11
12use crate::reference::Reference;
13use citum_schema::Style;
14use citum_schema::locale::Locale;
15use citum_schema::options::{Config, Substitute};
16use std::borrow::Cow;
17
18/// Matcher for determining if references share the same primary contributors.
19///
20/// Uses the style's substitution configuration to determine which contributor
21/// (author, editor, translator) should be used for comparison.
22pub struct Matcher<'a> {
23    /// The active citation style.
24    style: &'a Style,
25    /// The effective style configuration used for name resolution and fallbacks.
26    config: &'a Config,
27    /// The locale used to resolve multilingual and merged-list names.
28    locale: &'a Locale,
29}
30
31impl<'a> Matcher<'a> {
32    /// Build a matcher from the active style, effective configuration, and locale.
33    #[must_use]
34    pub fn new(style: &'a Style, config: &'a Config, locale: &'a Locale) -> Self {
35        Self {
36            style,
37            config,
38            locale,
39        }
40    }
41
42    /// Check if primary contributors (authors/editors) match between two references.
43    ///
44    /// Delegates to the shared effective-primary resolver
45    /// ([`crate::values::contributor::substitute::effective_primary_names`]) so
46    /// matching honors type overrides and merged-role candidates identically to
47    /// rendering, sorting, and disambiguation. Two references match only when
48    /// both resolve non-empty, equal name lists; a title-substitute result
49    /// (empty names) never matches.
50    #[must_use]
51    pub fn contributors_match(&self, prev: &Reference, current: &Reference) -> bool {
52        let substitute = self.get_substitute_config();
53        let prev_names = crate::values::contributor::substitute::effective_primary_names(
54            prev,
55            substitute.as_ref(),
56            self.config,
57            self.locale,
58        );
59        let curr_names = crate::values::contributor::substitute::effective_primary_names(
60            current,
61            substitute.as_ref(),
62            self.config,
63            self.locale,
64        );
65        !prev_names.is_empty() && !curr_names.is_empty() && prev_names == curr_names
66    }
67
68    /// Gets the substitute configuration from the style or falls back to defaults.
69    ///
70    /// Resolves the substitute template from the style's options if available,
71    /// otherwise falls back to the default configuration's substitute settings.
72    fn get_substitute_config(&self) -> Cow<'_, Substitute> {
73        if let Some(config) = self
74            .style
75            .options
76            .as_ref()
77            .and_then(|o| o.substitute.as_ref())
78        {
79            return config.resolve_ref();
80        }
81        self.config.substitute.as_ref().map_or_else(
82            || Cow::Owned(Substitute::default()),
83            citum_schema::options::SubstituteConfig::resolve_ref,
84        )
85    }
86}