citum_engine/values/
message.rs1use crate::reference::Reference;
13use crate::values::{ComponentValues, ProcHints, ProcValues, RenderOptions};
14use citum_schema::locale::{MessageArgs, MessageEvaluator, Mf2MessageEvaluator};
15use citum_schema::template::{MessageArgSource, TemplateMessage};
16use std::collections::HashMap;
17
18pub(crate) fn resolve_template_message_value(
24 component: &TemplateMessage,
25 config: &citum_schema::options::Config,
26 locale: &citum_schema::locale::Locale,
27) -> Option<String> {
28 let args = MessageArgs::default();
29 let mut value = if let Some(pattern) = config.messages.get(&component.message) {
30 Mf2MessageEvaluator.evaluate(pattern, &args)?
31 } else {
32 locale.resolve_template_message(
33 &component.message,
34 &args,
35 component.form.as_ref(),
36 component.gender.clone(),
37 )?
38 };
39
40 if component
41 .rendering
42 .strip_periods
43 .or(config.strip_periods)
44 .unwrap_or(false)
45 {
46 value = crate::values::strip_trailing_periods(&value);
47 }
48
49 if let Some(text_case) = component.rendering.text_case {
50 value = crate::values::text_case::apply_text_case_with_language(
51 &value,
52 text_case,
53 Some(locale.locale.as_str()),
54 );
55 }
56
57 (!value.trim().is_empty()).then_some(value)
58}
59
60impl ComponentValues for TemplateMessage {
61 fn values<F: crate::render::format::OutputFormat<Output = String>>(
62 &self,
63 reference: &Reference,
64 hints: &ProcHints,
65 options: &RenderOptions<'_>,
66 ) -> Option<ProcValues<F::Output>> {
67 if self.args.is_empty() {
68 let value =
69 resolve_template_message_value(self, options.config.as_ref(), options.locale)?;
70 let term_backed = self.message.starts_with("term.");
71 return Some(ProcValues {
72 value,
73 pre_formatted: !term_backed,
74 ..Default::default()
75 });
76 }
77
78 let mut named = HashMap::with_capacity(self.args.len());
79
80 for (name, source) in &self.args {
81 let value = render_message_arg::<F>(source, reference, hints, options)?;
82 if value.trim().is_empty() {
83 return None;
84 }
85 named.insert(name.clone(), value);
86 }
87
88 let args = MessageArgs {
89 named,
90 ..MessageArgs::default()
91 };
92 let mut value = if let Some(pattern) = options.config.messages.get(&self.message) {
93 Mf2MessageEvaluator.evaluate(pattern, &args)?
94 } else {
95 options.locale.resolve_template_message(
96 &self.message,
97 &args,
98 self.form.as_ref(),
99 self.gender.clone(),
100 )?
101 };
102
103 if crate::values::should_strip_periods(&self.rendering, options) {
104 value = crate::values::strip_trailing_periods(&value);
105 }
106
107 if let Some(tc) = self.rendering.text_case {
108 value = crate::values::text_case::apply_text_case_with_language(
109 &value,
110 tc,
111 Some(options.locale.locale.as_str()),
112 );
113 }
114
115 if value.trim().is_empty() {
116 return None;
117 }
118 let term_backed = self.message.starts_with("term.");
119
120 Some(ProcValues {
121 value,
122 pre_formatted: !term_backed,
123 ..Default::default()
124 })
125 }
126}
127
128fn render_message_arg<F: crate::render::format::OutputFormat<Output = String>>(
129 source: &MessageArgSource,
130 reference: &Reference,
131 hints: &ProcHints,
132 options: &RenderOptions<'_>,
133) -> Option<String> {
134 if let MessageArgSource::Literal { literal } = source {
135 return Some(literal.clone());
136 }
137 if let MessageArgSource::ReferenceType { .. } = source {
138 return Some(reference.ref_type());
139 }
140 if let MessageArgSource::Carrier { carrier } = source {
141 return Some(reference.medium().unwrap_or_else(|| {
142 let online = reference.url().is_some()
143 || reference.doi().is_some()
144 || reference.identifier("cstr").is_some();
145 if online {
146 carrier.online.clone()
147 } else {
148 carrier.absent.clone()
149 }
150 }));
151 }
152
153 let component = source.as_template_component()?;
154 if component.rendering().suppress == Some(true) {
155 return None;
156 }
157
158 let values = component.values::<F>(reference, hints, options)?;
159 if values.value.trim().is_empty() {
160 return None;
161 }
162
163 let fmt = F::default();
164 let proc_item = crate::render::ProcTemplateComponent {
165 template_component: component.clone(),
166 template_index: options.current_template_index,
167 value: values.value,
168 prefix: values.prefix,
169 suffix: values.suffix,
170 url: values.url,
171 ref_type: Some(reference.ref_type().clone()),
172 config: Some(options.config.clone()),
173 bibliography_config: options.bibliography_config.clone(),
174 item_language: crate::values::effective_component_language(reference, &component),
175 quote_marks: crate::render::format::QuoteMarks::from(options.locale),
176 sentence_initial: false,
177 pre_formatted: values.pre_formatted,
178 };
179
180 let rendered = crate::render::render_component_with_format_and_renderer::<F>(
181 &proc_item,
182 &fmt,
183 options.show_semantics,
184 );
185 if rendered.trim().is_empty() {
186 None
187 } else {
188 Some(rendered)
189 }
190}