1use mf2_catalog::{Catalog, StrRef};
5use mf2_model::{Dir, MarkupKind};
6
7use crate::function::{FnContext, Function, OptionEntries};
8use crate::sink::{Sink, SubPartSink};
9use crate::text::write_escaped_literal;
10use crate::unannotated;
11use crate::value::Value;
12
13pub trait PartSink {
15 fn part(&mut self, part: Part<'_>);
17
18 #[doc(hidden)]
25 fn part_catalog_text(&mut self, catalog: &Catalog, r: StrRef) -> bool {
26 match catalog.text(r) {
27 Some(s) => {
28 if !s.is_empty() {
29 self.part(Part::Text(s));
30 }
31 true
32 }
33 None => false,
34 }
35 }
36}
37
38#[derive(Debug)]
41#[non_exhaustive]
42pub enum Part<'p> {
43 Text(&'p str),
45 BidiIsolation(Isolation),
47 Expression(ExpressionPart<'p>),
49 Markup(MarkupPart<'p>),
51 Fallback(FallbackSource<'p>),
53}
54
55#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
57pub enum Isolation {
58 Lri,
60 Rli,
62 Fsi,
64 Pdi,
66}
67
68impl Isolation {
69 pub const fn as_str(self) -> &'static str {
71 match self {
72 Isolation::Lri => "\u{2066}",
73 Isolation::Rli => "\u{2067}",
74 Isolation::Fsi => "\u{2068}",
75 Isolation::Pdi => "\u{2069}",
76 }
77 }
78}
79
80pub struct ExpressionPart<'p> {
82 pub(crate) value: &'p Value<'p>,
83 pub(crate) handler: Option<&'static dyn Function>,
84 pub(crate) cx: FnContext<'p>,
85 pub(crate) dir: Dir,
86 pub(crate) id: Option<&'p str>,
87}
88
89impl<'p> ExpressionPart<'p> {
90 pub fn kind(&self) -> &'p str {
93 match self.handler {
94 Some(h) => h.part_kind(),
95 None => unannotated::kind(self.value),
96 }
97 }
98
99 pub fn locale(&self) -> &'p str {
101 self.cx.locale()
102 }
103
104 pub fn dir(&self) -> Dir {
106 self.dir
107 }
108
109 pub fn id(&self) -> Option<&'p str> {
111 self.id
112 }
113
114 pub fn value(&self) -> &'p Value<'p> {
116 self.value
117 }
118
119 pub fn write(&self, out: &mut dyn Sink) {
121 match self.handler {
122 Some(h) => h.format(&self.cx, self.value, out),
123 None => unannotated::format(self.value, self.cx.host(), out),
124 }
125 }
126
127 pub fn sub_parts(&self, out: &mut dyn SubPartSink) {
130 match self.handler {
131 Some(h) => h.format_parts(&self.cx, self.value, out),
132 None => unannotated::format_parts(self.value, self.cx.host(), out),
133 }
134 }
135}
136
137impl core::fmt::Debug for ExpressionPart<'_> {
140 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
141 f.debug_struct("ExpressionPart")
142 .field("value", self.value)
143 .field("dir", &self.dir)
144 .field("id", &self.id)
145 .finish_non_exhaustive()
146 }
147}
148
149pub struct MarkupPart<'p> {
151 pub(crate) kind: MarkupKind,
152 pub(crate) name: &'p str,
153 pub(crate) id: Option<&'p str>,
154 pub(crate) options: OptionEntries<'p, 'p>,
155}
156
157impl<'p> MarkupPart<'p> {
158 pub fn kind(&self) -> MarkupKind {
160 self.kind
161 }
162
163 pub fn name(&self) -> &'p str {
165 self.name
166 }
167
168 pub fn id(&self) -> Option<&'p str> {
170 self.id
171 }
172
173 pub fn options(&self) -> MarkupOptions<'p> {
175 MarkupOptions {
176 entries: self.options,
177 at: 0,
178 }
179 }
180}
181
182impl core::fmt::Debug for MarkupPart<'_> {
184 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
185 f.debug_struct("MarkupPart")
186 .field("kind", &self.kind)
187 .field("name", &self.name)
188 .field("id", &self.id)
189 .field("options", &self.options.len())
190 .finish()
191 }
192}
193
194pub struct MarkupOptions<'p> {
196 entries: OptionEntries<'p, 'p>,
197 at: usize,
198}
199
200impl core::fmt::Debug for MarkupOptions<'_> {
202 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
203 f.debug_struct("MarkupOptions")
204 .field("left", &self.entries.len().saturating_sub(self.at))
205 .finish()
206 }
207}
208
209impl<'p> Iterator for MarkupOptions<'p> {
210 type Item = (&'p str, &'p Value<'p>);
211
212 fn next(&mut self) -> Option<Self::Item> {
213 let e = self.entries.get(self.at)?;
214 self.at += 1;
215 Some((e.0, e.1.value))
216 }
217}
218
219#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
221#[non_exhaustive]
222pub enum FallbackSource<'p> {
223 Variable(&'p str),
225 Literal(&'p str),
227 Function(&'p str),
229 Unknown,
231}
232
233impl FallbackSource<'_> {
234 pub fn write(&self, out: &mut dyn Sink) {
236 match *self {
237 FallbackSource::Variable(n) => {
238 out.push_str("$");
239 out.push_str(n);
240 }
241 FallbackSource::Literal(v) => {
242 out.push_str("|");
243 write_escaped_literal(v, out);
244 out.push_str("|");
245 }
246 FallbackSource::Function(n) => {
247 out.push_str(":");
248 out.push_str(n);
249 }
250 FallbackSource::Unknown => out.push_str("\u{FFFD}"),
251 }
252 }
253}