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                sentence_initial: false,
54                pre_formatted: v.pre_formatted,
55            };
56
57            let rendered = crate::render::render_component_with_format_and_renderer::<F>(
58                &proc_item,
59                &fmt,
60                options.show_semantics,
61            );
62            if !rendered.is_empty() {
63                values.push(rendered);
64            }
65        }
66
67        if values.is_empty() || !has_content {
68            return None;
69        }
70
71        // Join with delimiter
72        let delimiter = self
73            .delimiter
74            .as_ref()
75            .unwrap_or(&DelimiterPunctuation::Comma)
76            .to_string_with_space();
77
78        Some(ProcValues {
79            value: fmt.join(values, &delimiter),
80            prefix: None,
81            suffix: None,
82            url: None,
83            substituted_key: None,
84            pre_formatted: true,
85        })
86    }
87}
88
89/// Returns true if the component value is derived from a locale term rather than a data field.
90fn is_term_based(component: &citum_schema::template::TemplateComponent) -> bool {
91    use citum_schema::template::TemplateComponent;
92    match component {
93        TemplateComponent::Term(_) => true,
94        TemplateComponent::Group(l) => l.group.iter().all(is_term_based),
95        _ => false,
96    }
97}