Skip to main content

citum_engine/values/
list.rs

1/*
2SPDX-License-Identifier: MIT OR Apache-2.0
3SPDX-FileCopyrightText: © 2023-2026 Bruce D'Arcus and Citum contributors
4*/
5
6//! Rendering logic for list components with configurable delimiters.
7//!
8//! This module handles rendering of lists of template items, with support for
9//! different delimiters between items (commas, semicolons, etc.) and rendering modes.
10
11use crate::reference::Reference;
12use crate::values::{ComponentValues, ProcHints, ProcValues, RenderOptions};
13use citum_schema::template::{DelimiterPunctuation, TemplateGroup};
14
15impl ComponentValues for TemplateGroup {
16    /// Returns the resolved list of component values for rendering.
17    fn values<F: crate::render::format::OutputFormat<Output = String>>(
18        &self,
19        reference: &Reference,
20        hints: &ProcHints,
21        options: &RenderOptions<'_>,
22    ) -> Option<ProcValues<F::Output>> {
23        let mut has_content = false;
24        let fmt = F::default();
25
26        // Collect values from all items, applying their rendering
27        let mut values = Vec::with_capacity(self.group.len());
28        for item in &self.group {
29            let Some(v) = item.values::<F>(reference, hints, options) else {
30                continue;
31            };
32            if v.value.is_empty() {
33                continue;
34            }
35
36            // Track if we have any "meaningful" content (not just a term)
37            if !is_term_based(item) {
38                has_content = true;
39            }
40
41            // Use the central rendering logic to apply global config, local settings, and overrides
42            let proc_item = crate::render::ProcTemplateComponent {
43                template_component: item.clone(),
44                template_index: options.current_template_index,
45                value: v.value,
46                prefix: v.prefix,
47                suffix: v.suffix,
48                url: v.url,
49                ref_type: Some(reference.ref_type().clone()),
50                config: Some(options.config.clone()),
51                bibliography_config: options.bibliography_config.clone(),
52                item_language: crate::values::effective_component_language(reference, item),
53                quote_marks: crate::render::format::QuoteMarks::from(
54                    &options.locale.grammar_options,
55                ),
56                sentence_initial: false,
57                pre_formatted: v.pre_formatted,
58            };
59
60            let rendered = crate::render::render_component_with_format_and_renderer::<F>(
61                &proc_item,
62                &fmt,
63                options.show_semantics,
64            );
65            if !rendered.is_empty() {
66                values.push(rendered);
67            }
68        }
69
70        if values.is_empty() || !has_content {
71            return None;
72        }
73
74        // Join with delimiter
75        let delimiter = self
76            .delimiter
77            .as_ref()
78            .unwrap_or(&DelimiterPunctuation::Comma)
79            .to_string_with_space();
80
81        Some(ProcValues {
82            value: fmt.join(values, &delimiter),
83            prefix: None,
84            suffix: None,
85            url: None,
86            substituted_key: None,
87            pre_formatted: true,
88        })
89    }
90}
91
92/// Returns true if the component value is derived from a locale term rather than a data field.
93fn is_term_based(component: &citum_schema::template::TemplateComponent) -> bool {
94    use citum_schema::template::TemplateComponent;
95    match component {
96        TemplateComponent::Term(_) => true,
97        TemplateComponent::Message(message) if message.message.starts_with("term.") => true,
98        TemplateComponent::Group(l) => l.group.iter().all(is_term_based),
99        _ => false,
100    }
101}