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