1use core::fmt;
20
21use alloc::{borrow::Cow, string::String, vec::Vec};
22
23use crate::tree::{
24 codec::{
25 encode::encode_component,
26 escape::escape_with,
27 mode::Escaper,
28 unescape::{unescape_bytes, unescape_with},
29 },
30 leaf::IcalValueLeaf,
31};
32
33#[derive(Clone, Debug, Default)]
44pub struct IcalValueNode<'a> {
45 raw: Option<Cow<'a, [u8]>>,
49 components: Vec<Vec<IcalValueLeaf<'a>>>,
52 pub escaper: Escaper,
54}
55
56impl<'a> IcalValueNode<'a> {
57 pub fn parse(value: &'a [u8]) -> Self {
61 Self {
62 raw: Some(Cow::Borrowed(value)),
63 components: Vec::new(),
64 escaper: Escaper::default(),
65 }
66 }
67
68 pub(crate) fn from_components(
71 components: Vec<Vec<IcalValueLeaf<'a>>>,
72 escaper: Escaper,
73 ) -> Self {
74 Self {
75 raw: None,
76 components,
77 escaper,
78 }
79 }
80
81 pub fn component_count(&self) -> usize {
84 match &self.raw {
85 Some(raw) => {
86 let mut count = 0;
87 split_on(raw, b';', |_| count += 1);
88 count
89 }
90 None => self.components.len(),
91 }
92 }
93
94 pub fn decode_at(&self, i: usize) -> Vec<Cow<'_, str>> {
96 match self.component_at(i) {
97 Some(Component::Raw(bytes)) => {
98 let mut values = Vec::new();
99 split_on(bytes, b',', |value| {
100 values.push(unescape_with(value, self.escaper))
101 });
102 values
103 }
104 Some(Component::Split(leaves)) => leaves
105 .iter()
106 .map(|leaf| unescape_with(leaf.as_bytes(), self.escaper))
107 .collect(),
108 None => Vec::new(),
109 }
110 }
111
112 pub fn decode_bytes_at(&self, i: usize) -> Cow<'_, [u8]> {
115 match self.component_at(i) {
116 Some(Component::Raw(bytes)) => unescape_bytes(first_value(bytes), self.escaper),
117 Some(Component::Split(leaves)) => leaves
118 .first()
119 .map(|leaf| unescape_bytes(leaf.as_bytes(), self.escaper))
120 .unwrap_or(Cow::Borrowed(b"")),
121 None => Cow::Borrowed(b""),
122 }
123 }
124
125 pub fn decode_scalar_at(&self, i: usize) -> Cow<'_, str> {
127 match self.component_at(i) {
128 Some(Component::Raw(bytes)) => unescape_with(first_value(bytes), self.escaper),
129 Some(Component::Split(leaves)) => leaves
130 .first()
131 .map(|leaf| unescape_with(leaf.as_bytes(), self.escaper))
132 .unwrap_or(Cow::Borrowed("")),
133 None => Cow::Borrowed(""),
134 }
135 }
136
137 pub fn decode_joined_at(&self, i: usize) -> Cow<'_, str> {
141 match self.component_at(i) {
142 Some(Component::Raw(bytes)) => unescape_with(bytes, self.escaper),
145 Some(Component::Split(leaves)) => {
146 if leaves.len() <= 1 {
147 return leaves
148 .first()
149 .map(|leaf| unescape_with(leaf.as_bytes(), self.escaper))
150 .unwrap_or(Cow::Borrowed(""));
151 }
152
153 let mut raw = Vec::new();
154 for (j, leaf) in leaves.iter().enumerate() {
155 if j > 0 {
156 raw.push(b',');
157 }
158 raw.extend_from_slice(leaf.as_bytes());
159 }
160
161 Cow::Owned(unescape_with(&raw, self.escaper).into_owned())
162 }
163 None => Cow::Borrowed(""),
164 }
165 }
166
167 pub(crate) fn first_value_bytes(&self) -> &[u8] {
170 match self.component_at(0) {
171 Some(Component::Raw(bytes)) => first_value(bytes),
172 Some(Component::Split(leaves)) => {
173 leaves.first().map(|leaf| leaf.as_bytes()).unwrap_or(b"")
174 }
175 None => b"",
176 }
177 }
178
179 pub fn set_at<S: AsRef<str>>(&mut self, i: usize, values: &[S]) {
183 self.materialize();
184
185 while self.components.len() <= i {
186 self.components.push(Vec::new());
187 }
188
189 self.components[i] = encode_component(values, self.escaper);
190 }
191
192 pub fn set_bytes_at<B: AsRef<[u8]>>(&mut self, i: usize, values: &[B]) {
195 self.materialize();
196
197 while self.components.len() <= i {
198 self.components.push(Vec::new());
199 }
200
201 self.components[i] = values
202 .iter()
203 .map(|v| IcalValueLeaf::from(escape_with(v.as_ref(), self.escaper).into_owned()))
204 .collect();
205 }
206
207 pub(crate) fn write_bytes(&self, out: &mut Vec<u8>) {
211 if let Some(raw) = &self.raw {
212 out.extend_from_slice(raw);
213 return;
214 }
215
216 for (i, component) in self.components.iter().enumerate() {
217 if i > 0 {
218 out.push(b';');
219 }
220
221 for (j, leaf) in component.iter().enumerate() {
222 if j > 0 {
223 out.push(b',');
224 }
225
226 out.extend_from_slice(leaf.as_bytes());
227 }
228 }
229 }
230
231 pub(crate) fn into_static(self) -> IcalValueNode<'static> {
234 match self.raw {
235 Some(raw) => IcalValueNode {
236 raw: Some(Cow::Owned(raw.into_owned())),
237 components: Vec::new(),
238 escaper: self.escaper,
239 },
240 None => IcalValueNode {
241 raw: None,
242 components: self
243 .components
244 .into_iter()
245 .map(|component| {
246 component
247 .into_iter()
248 .map(IcalValueLeaf::into_static)
249 .collect()
250 })
251 .collect(),
252 escaper: self.escaper,
253 },
254 }
255 }
256
257 fn component_at(&self, i: usize) -> Option<Component<'_, 'a>> {
260 match &self.raw {
261 Some(raw) => {
262 let mut found = None;
263 let mut index = 0;
264 split_on(raw, b';', |component| {
265 if index == i {
266 found = Some(component);
267 }
268 index += 1;
269 });
270 found.map(Component::Raw)
271 }
272 None => self
273 .components
274 .get(i)
275 .map(|leaves| Component::Split(leaves)),
276 }
277 }
278
279 fn materialize(&mut self) {
282 let Some(raw) = self.raw.take() else {
283 return;
284 };
285
286 self.components = match raw {
287 Cow::Borrowed(bytes) => split_all(bytes),
288 Cow::Owned(bytes) => split_all_owned(&bytes),
289 };
290 }
291}
292
293impl fmt::Display for IcalValueNode<'_> {
294 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295 if let Some(raw) = &self.raw {
296 return f.write_str(&String::from_utf8_lossy(raw));
297 }
298
299 for (i, component) in self.components.iter().enumerate() {
300 if i > 0 {
301 f.write_str(";")?;
302 }
303
304 for (j, leaf) in component.iter().enumerate() {
305 if j > 0 {
306 f.write_str(",")?;
307 }
308
309 f.write_str(&String::from_utf8_lossy(leaf.as_bytes()))?;
310 }
311 }
312
313 Ok(())
314 }
315}
316
317enum Component<'s, 'a> {
320 Raw(&'s [u8]),
323 Split(&'s [IcalValueLeaf<'a>]),
325}
326
327fn first_value(component: &[u8]) -> &[u8] {
330 let mut first = None;
331 split_on(component, b',', |value| {
332 first.get_or_insert(value);
333 });
334 first.unwrap_or(component)
335}
336
337fn split_all(bytes: &[u8]) -> Vec<Vec<IcalValueLeaf<'_>>> {
340 let mut components = Vec::new();
341 split_on(bytes, b';', |component| {
342 let mut values = Vec::new();
343 split_on(component, b',', |value| {
344 values.push(IcalValueLeaf::from(value));
345 });
346 components.push(values);
347 });
348 components
349}
350
351fn split_all_owned(bytes: &[u8]) -> Vec<Vec<IcalValueLeaf<'static>>> {
354 let mut components = Vec::new();
355 split_on(bytes, b';', |component| {
356 let mut values = Vec::new();
357 split_on(component, b',', |value| {
358 values.push(IcalValueLeaf::from(value.to_vec()));
359 });
360 components.push(values);
361 });
362 components
363}
364
365fn split_on<'b>(bytes: &'b [u8], sep: u8, mut piece: impl FnMut(&'b [u8])) {
371 let mut start = 0;
372 let mut i = 0;
373
374 while let Some(offset) = memchr::memchr2(b'\\', sep, &bytes[i..]) {
375 let pos = i + offset;
376 if bytes[pos] == b'\\' {
377 i = (pos + 2).min(bytes.len());
378 } else {
379 piece(&bytes[start..pos]);
380 start = pos + 1;
381 i = pos + 1;
382 }
383 }
384
385 piece(&bytes[start..]);
386}
387
388#[cfg(test)]
389mod tests {
390 use alloc::{borrow::Cow, string::ToString, vec};
391
392 use crate::tree::value::node::IcalValueNode;
393
394 #[test]
395 fn splits_components_and_values_then_round_trips() {
396 let node = IcalValueNode::parse(b"a;b,c;");
397 assert_eq!(node.component_count(), 3);
398 assert_eq!(
399 node.decode_at(1),
400 vec![Cow::Borrowed("b"), Cow::Borrowed("c")]
401 );
402 assert_eq!(node.to_string(), "a;b,c;");
403 }
404
405 #[test]
406 fn keeps_escaped_separators_inside_one_value() {
407 let node = IcalValueNode::parse(br"a\,b\;c;d");
408 assert_eq!(node.component_count(), 2);
409 assert_eq!(node.decode_at(0).len(), 1);
410 assert_eq!(node.to_string(), r"a\,b\;c;d");
411 }
412
413 #[test]
414 fn an_edit_splits_and_preserves_untouched_components() {
415 let mut node = IcalValueNode::parse(b"a;b;c");
416 node.set_at(1, &["X"]);
417 assert_eq!(node.to_string(), "a;X;c");
418 }
419}