Skip to main content

ical/tree/value/
node.rs

1//! # Value node
2//!
3//! The raw value of a content line, on the syntax side.
4//!
5//! [`IcalValueNode`] is the syntactic peer of the decoded
6//! [`IcalValue`](crate::value::IcalValue): the bytes after a line's colon, read
7//! as `;`-separated components of `,`-separated
8//! [`IcalValueLeaf`](crate::tree::leaf) values (raw bytes, so a foreign charset
9//! survives). Straight from parse the value stays one unsplit slice walked on
10//! demand; an edit or a model encode splits it into owned components, which then
11//! become the source of truth. The splitting is generic, counting and preserving
12//! separators so the value round-trips; what the components *mean* is the lens's
13//! business.
14//!
15//! The codec that unescapes components into decoded values and re-escapes edits
16//! back lives on this type, applying the rules of the sibling
17//! [`mode`](crate::tree::codec::mode) codec.
18
19use 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/// A raw value: `;`-separated components, each a list of `,`-separated raw
34/// value leaves.
35///
36/// From parse the value is held as one unsplit `raw` slice and
37/// walked lazily, so a parse that never decodes and a byte-faithful reserialize
38/// do no splitting at all. The first edit (or a model encode) splits it into
39/// owned `components`, which then take over as the source of
40/// truth. The `escaper` records which version's escaping rules the codec must
41/// apply; it is stamped from the calendar version after parsing (see
42/// `IcalCst::parse`).
43#[derive(Clone, Debug, Default)]
44pub struct IcalValueNode<'a> {
45    /// The unsplit value bytes, straight from parse and authoritative until an
46    /// edit or a model encode replaces them; `None` once `components` is the
47    /// source of truth.
48    raw: Option<Cow<'a, [u8]>>,
49    /// The split components, authoritative when `raw` is `None`; while `raw` is
50    /// `Some` this stays empty and reads split `raw` on demand.
51    components: Vec<Vec<IcalValueLeaf<'a>>>,
52    /// The escaping rules to read and write this value with.
53    pub escaper: Escaper,
54}
55
56impl<'a> IcalValueNode<'a> {
57    /// Wrap a raw value, unsplit and borrowed, to be walked lazily. The colon,
58    /// name and eol are the line's business; this is only the bytes after the
59    /// colon.
60    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    /// Build a value node from already-split components (the model encode
69    /// path); there is no `raw`, so the components are the source of truth.
70    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    /// The number of `;`-separated components (at least one, since an empty
82    /// value is one empty component).
83    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    /// Decode the `i`th component into a clean (unescaped) value list.
95    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    /// The `i`th component's first value as raw unescaped bytes, not
113    /// transcoded, for a value carrying a foreign charset.
114    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    /// Decode the `i`th component's first value (empty when there is none).
126    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    /// Decode the `i`th component as a single value, keeping its `,`-separated
138    /// pieces joined. For values like URIs whose comma is a literal part of the
139    /// value, not a list separator (so they must not be truncated).
140    pub fn decode_joined_at(&self, i: usize) -> Cow<'_, str> {
141        match self.component_at(i) {
142            // NOTE: The whole component slice already has the commas in place,
143            // so unescaping it verbatim keeps them literal.
144            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    /// The raw (still-escaped) bytes of the first component's first value, for
168    /// the simple single-value lines (the envelope values and diagnostics).
169    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    /// Set the `i`th component, escaping each value. Pads with empty components
180    /// when needed; every other component is left untouched, so a parsed calendar
181    /// keeps its bytes.
182    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    /// Set the `i`th component from raw value bytes (the foreign-charset escape
193    /// hatch), escaping structural separators but writing the bytes verbatim.
194    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    /// Serialize the raw value bytes (name, colon and eol are the line's job)
208    /// into `out`, exactly as parsed. An untouched value emits its `raw` slice
209    /// with no reassembly.
210    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    /// Convert into an owned value node (`'static`), keeping it lazy: an
232    /// unsplit value stays unsplit, only its bytes become owned.
233    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    /// Locate the `i`th component, either as a raw slice of the unsplit value
258    /// or as the already-split leaves.
259    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    /// Split the unsplit `raw` value into owned components so it can be edited
280    /// in place; a no-op once already split. Only edits pay this cost.
281    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
317/// One located component: a slice of the still-unsplit value, or its leaves
318/// once the value has been split for editing.
319enum Component<'s, 'a> {
320    /// The component's bytes, still `,`-joined, borrowed from the unsplit
321    /// value.
322    Raw(&'s [u8]),
323    /// The component's already-split leaves.
324    Split(&'s [IcalValueLeaf<'a>]),
325}
326
327/// The first `,`-separated value of a component (escape-aware): the bytes up to
328/// the first unescaped comma, or the whole component when it has none.
329fn 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
337/// Split a value into its `;`-separated components, each a list of its
338/// `,`-separated leaves, borrowing from `bytes`.
339fn 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
351/// Split like [`split_all`] but copying each leaf, for the owned bytes an
352/// edit-after-`into_static` value carries (which no slice can borrow from).
353fn 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
365/// Call `piece` for each span between unescaped `sep` bytes, always at least
366/// once. A backslash escapes the next byte (so `\;` / `\,` do not split), and
367/// `memchr` skips straight to the next `sep` or backslash instead of scanning
368/// byte by byte, so a large separator-free value (e.g. base64) is skipped in
369/// one pass.
370fn 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}