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
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    fn values<F: crate::render::format::OutputFormat<Output = String>>(
17        &self,
18        reference: &Reference,
19        hints: &ProcHints,
20        options: &RenderOptions<'_>,
21    ) -> Option<ProcValues<F::Output>> {
22        let mut has_content = false;
23        let fmt = F::default();
24
25        // Collect values from all items, applying their rendering
26        let values: Vec<F::Output> = self
27            .group
28            .iter()
29            .filter_map(|item| {
30                let v = item.values::<F>(reference, hints, options)?;
31                if v.value.is_empty() {
32                    return None;
33                }
34
35                // Track if we have any "meaningful" content (not just a term)
36                if !is_term_based(item) {
37                    has_content = true;
38                }
39
40                // Use the central rendering logic to apply global config, local settings, and overrides
41                let proc_item = crate::render::ProcTemplateComponent {
42                    template_component: item.clone(),
43                    template_index: options.current_template_index,
44                    value: v.value,
45                    prefix: v.prefix,
46                    suffix: v.suffix,
47                    url: v.url,
48                    ref_type: Some(reference.ref_type().clone()),
49                    config: Some(options.config.clone()),
50                    bibliography_config: options.bibliography_config.clone(),
51                    item_language: crate::values::effective_component_language(reference, item),
52                    sentence_initial: false,
53                    pre_formatted: v.pre_formatted,
54                };
55
56                let rendered = crate::render::render_component_with_format_and_renderer::<F>(
57                    &proc_item,
58                    &fmt,
59                    options.show_semantics,
60                );
61                if rendered.is_empty() {
62                    None
63                } else {
64                    Some(rendered)
65                }
66            })
67            .collect();
68
69        if values.is_empty() || !has_content {
70            return None;
71        }
72
73        // Join with delimiter
74        let delimiter = self
75            .delimiter
76            .as_ref()
77            .unwrap_or(&DelimiterPunctuation::Comma)
78            .to_string_with_space();
79
80        Some(ProcValues {
81            value: fmt.join(values, &delimiter),
82            prefix: None,
83            suffix: None,
84            url: None,
85            substituted_key: None,
86            pre_formatted: true,
87        })
88    }
89}
90
91/// Check if a component is purely term-based or a list of such.
92fn is_term_based(component: &citum_schema::template::TemplateComponent) -> bool {
93    use citum_schema::template::TemplateComponent;
94    match component {
95        TemplateComponent::Term(_) => true,
96        TemplateComponent::Group(l) => l.group.iter().all(is_term_based),
97        _ => false,
98    }
99}