Skip to main content

vcard/tree/value/
node.rs

1//! # Value node
2//!
3//! The raw value of a content line, on the syntax side.
4//!
5//! [`VcardValueNode`] is the syntactic peer of the decoded
6//! [`VcardValue`](crate::value::VcardValue): the bytes after a line's colon,
7//! read as `;`-separated components of `,`-separated
8//! [`crate::tree::leaf::VcardValueLeaf`] values (raw bytes, so a foreign
9//! charset survives). Straight from parse the value stays one unsplit slice
10//! walked on demand; an edit or a model encode splits it into owned components,
11//! which then become the source of truth. The splitting is generic, counting
12//! and preserving separators so the value round-trips; what the components
13//! *mean* is the lens's 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, encode_leaf},
26        escape::escape_with,
27        mode::VcardEscaper,
28        unescape::{unescape_bytes, unescape_with},
29    },
30    leaf::VcardValueLeaf,
31};
32
33/// A raw value: `;`-separated components, each a list of `,`-separated raw
34/// value leaves.
35///
36/// From parse it is one unsplit `raw` slice, walked lazily, so a parse that
37/// never decodes and a byte-faithful reserialize split nothing. The first edit
38/// (or a model encode) splits it into owned `components`, which then take over.
39/// A write touches only the leaf or component it names, so everything else
40/// keeps its parsed bytes. `escaper` records which version's escaping rules to
41/// apply, stamped from the card version after parsing.
42#[derive(Clone, Debug, Default)]
43pub struct VcardValueNode<'a> {
44    /// The unsplit value bytes, straight from parse and authoritative until an
45    /// edit or a model encode replaces them; `None` once `components` is the
46    /// source of truth.
47    raw: Option<Cow<'a, [u8]>>,
48    /// The split components, authoritative when `raw` is `None`; while `raw` is
49    /// `Some` this stays empty and reads split `raw` on demand.
50    components: Vec<Vec<VcardValueLeaf<'a>>>,
51    /// The escaping rules to read and write this value with.
52    pub escaper: VcardEscaper,
53}
54
55impl<'a> VcardValueNode<'a> {
56    /// Wrap a raw value, unsplit and borrowed, to be walked lazily. The colon,
57    /// name and eol are the line's business; this is only the bytes after the
58    /// colon.
59    pub fn parse(value: &'a [u8]) -> Self {
60        Self {
61            raw: Some(Cow::Borrowed(value)),
62            components: Vec::new(),
63            escaper: VcardEscaper::default(),
64        }
65    }
66
67    /// Build a value node from already-split components (the model encode
68    /// path); there is no `raw`, so the components are the source of truth.
69    pub(crate) fn from_components(
70        components: Vec<Vec<VcardValueLeaf<'a>>>,
71        escaper: VcardEscaper,
72    ) -> Self {
73        Self {
74            raw: None,
75            components,
76            escaper,
77        }
78    }
79
80    /// The number of `;`-separated components (at least one, since an empty
81    /// value is one empty component).
82    pub fn component_count(&self) -> usize {
83        match &self.raw {
84            Some(raw) => {
85                let mut count = 0;
86                split_on(raw, b';', |_| count += 1);
87                count
88            }
89            None => self.components.len(),
90        }
91    }
92
93    /// Decode the `i`th component into a clean (unescaped) value list.
94    pub fn decode_at(&self, i: usize) -> Vec<Cow<'_, str>> {
95        match self.component_at(i) {
96            Some(Component::Raw(bytes)) => {
97                let mut values = Vec::new();
98                split_on(bytes, b',', |value| {
99                    values.push(unescape_with(value, self.escaper))
100                });
101                values
102            }
103            Some(Component::Split(leaves)) => leaves
104                .iter()
105                .map(|leaf| unescape_with(leaf.as_bytes(), self.escaper))
106                .collect(),
107            None => Vec::new(),
108        }
109    }
110
111    /// The `i`th component's first value as raw unescaped bytes, not
112    /// transcoded, for a value carrying a foreign charset.
113    pub fn decode_bytes_at(&self, i: usize) -> Cow<'_, [u8]> {
114        match self.component_at(i) {
115            Some(Component::Raw(bytes)) => unescape_bytes(first_value(bytes), self.escaper),
116            Some(Component::Split(leaves)) => leaves
117                .first()
118                .map(|leaf| unescape_bytes(leaf.as_bytes(), self.escaper))
119                .unwrap_or(Cow::Borrowed(b"")),
120            None => Cow::Borrowed(b""),
121        }
122    }
123
124    /// Decode the `i`th component's first value (empty when there is none).
125    pub fn decode_scalar_at(&self, i: usize) -> Cow<'_, str> {
126        match self.component_at(i) {
127            Some(Component::Raw(bytes)) => unescape_with(first_value(bytes), self.escaper),
128            Some(Component::Split(leaves)) => leaves
129                .first()
130                .map(|leaf| unescape_with(leaf.as_bytes(), self.escaper))
131                .unwrap_or(Cow::Borrowed("")),
132            None => Cow::Borrowed(""),
133        }
134    }
135
136    /// Decode the `i`th component as a single value, keeping its `,`-separated
137    /// pieces joined. For values like URIs whose comma is a literal part of the
138    /// value, not a list separator (so they must not be truncated).
139    pub fn decode_joined_at(&self, i: usize) -> Cow<'_, str> {
140        match self.component_at(i) {
141            // NOTE: The whole component slice already has the commas in place,
142            // so unescaping it verbatim keeps them literal.
143            Some(Component::Raw(bytes)) => unescape_with(bytes, self.escaper),
144            Some(Component::Split(leaves)) => {
145                if leaves.len() <= 1 {
146                    return leaves
147                        .first()
148                        .map(|leaf| unescape_with(leaf.as_bytes(), self.escaper))
149                        .unwrap_or(Cow::Borrowed(""));
150                }
151
152                let mut raw = Vec::new();
153                for (j, leaf) in leaves.iter().enumerate() {
154                    if j > 0 {
155                        raw.push(b',');
156                    }
157                    raw.extend_from_slice(leaf.as_bytes());
158                }
159
160                Cow::Owned(unescape_with(&raw, self.escaper).into_owned())
161            }
162            None => Cow::Borrowed(""),
163        }
164    }
165
166    /// The raw (still-escaped) bytes of the first component's first value, for
167    /// the simple single-value lines (the envelope values and diagnostics).
168    pub(crate) fn first_value_bytes(&self) -> &[u8] {
169        match self.component_at(0) {
170            Some(Component::Raw(bytes)) => first_value(bytes),
171            Some(Component::Split(leaves)) => {
172                leaves.first().map(|leaf| leaf.as_bytes()).unwrap_or(b"")
173            }
174            None => b"",
175        }
176    }
177
178    /// Set the `i`th component, escaping each value and padding with empty
179    /// components when needed.
180    pub fn set_at<S: AsRef<str>>(&mut self, i: usize, values: &[S]) {
181        self.materialize();
182
183        while self.components.len() <= i {
184            self.components.push(Vec::new());
185        }
186
187        self.components[i] = encode_component(values, self.escaper);
188    }
189
190    /// Set the `i`th component from raw value bytes (the foreign-charset escape
191    /// hatch), escaping structural separators but writing the bytes verbatim.
192    pub fn set_bytes_at<B: AsRef<[u8]>>(&mut self, i: usize, values: &[B]) {
193        self.materialize();
194
195        while self.components.len() <= i {
196            self.components.push(Vec::new());
197        }
198
199        self.components[i] = values
200            .iter()
201            .map(|v| VcardValueLeaf::from(escape_with(v.as_ref(), self.escaper).into_owned()))
202            .collect();
203    }
204
205    /// The number of `,`-separated values in the `i`th component (zero when the
206    /// component does not exist).
207    pub fn value_count(&self, i: usize) -> usize {
208        match self.component_at(i) {
209            Some(Component::Raw(bytes)) => {
210                let mut count = 0;
211                split_on(bytes, b',', |_| count += 1);
212                count
213            }
214            Some(Component::Split(leaves)) => leaves.len(),
215            None => 0,
216        }
217    }
218
219    /// Replace the `j`th value of the `i`th component in place, re-escaping only
220    /// that leaf. Pads with empty values when `j` is past the end.
221    pub fn set_value_at<S: AsRef<str>>(&mut self, i: usize, j: usize, value: S) {
222        let escaper = self.escaper;
223        let component = self.component_mut(i);
224
225        while component.len() <= j {
226            component.push(encode_leaf("", escaper));
227        }
228
229        component[j] = encode_leaf(value, escaper);
230    }
231
232    /// Insert a value at position `j` of the `i`th component (clamped to the
233    /// end), escaping only the new leaf.
234    pub fn insert_value_at<S: AsRef<str>>(&mut self, i: usize, j: usize, value: S) {
235        let escaper = self.escaper;
236        let component = self.component_mut(i);
237        let at = j.min(component.len());
238
239        component.insert(at, encode_leaf(value, escaper));
240    }
241
242    /// Append a value to the `i`th component, escaping only the new leaf.
243    pub fn push_value<S: AsRef<str>>(&mut self, i: usize, value: S) {
244        let escaper = self.escaper;
245        let component = self.component_mut(i);
246
247        component.push(encode_leaf(value, escaper));
248    }
249
250    /// Remove the `j`th value of the `i`th component, splicing it out; a no-op
251    /// when either index is out of range.
252    pub fn remove_value_at(&mut self, i: usize, j: usize) {
253        self.materialize();
254
255        if let Some(component) = self.components.get_mut(i)
256            && j < component.len()
257        {
258            component.remove(j);
259        }
260    }
261
262    /// Serialize the raw value bytes (name, colon and eol are the line's job)
263    /// into `out`, exactly as parsed. An untouched value emits its `raw` slice
264    /// with no reassembly.
265    pub(crate) fn write_bytes(&self, out: &mut Vec<u8>) {
266        if let Some(raw) = &self.raw {
267            out.extend_from_slice(raw);
268            return;
269        }
270
271        for (i, component) in self.components.iter().enumerate() {
272            if i > 0 {
273                out.push(b';');
274            }
275
276            for (j, leaf) in component.iter().enumerate() {
277                if j > 0 {
278                    out.push(b',');
279                }
280
281                out.extend_from_slice(leaf.as_bytes());
282            }
283        }
284    }
285
286    /// Convert into an owned value node (`'static`), keeping it lazy: an
287    /// unsplit value stays unsplit, only its bytes become owned.
288    pub(crate) fn into_static(self) -> VcardValueNode<'static> {
289        match self.raw {
290            Some(raw) => VcardValueNode {
291                raw: Some(Cow::Owned(raw.into_owned())),
292                components: Vec::new(),
293                escaper: self.escaper,
294            },
295            None => VcardValueNode {
296                raw: None,
297                components: self
298                    .components
299                    .into_iter()
300                    .map(|component| {
301                        component
302                            .into_iter()
303                            .map(VcardValueLeaf::into_static)
304                            .collect()
305                    })
306                    .collect(),
307                escaper: self.escaper,
308            },
309        }
310    }
311
312    /// Locate the `i`th component, either as a raw slice of the unsplit value
313    /// or as the already-split leaves.
314    fn component_at(&self, i: usize) -> Option<Component<'_, 'a>> {
315        match &self.raw {
316            Some(raw) => {
317                let mut found = None;
318                let mut index = 0;
319                split_on(raw, b';', |component| {
320                    if index == i {
321                        found = Some(component);
322                    }
323                    index += 1;
324                });
325                found.map(Component::Raw)
326            }
327            None => self
328                .components
329                .get(i)
330                .map(|leaves| Component::Split(leaves)),
331        }
332    }
333
334    /// Borrow the `i`th component's leaves for in-place editing, splitting the
335    /// value first and padding with empty components up to `i`.
336    fn component_mut(&mut self, i: usize) -> &mut Vec<VcardValueLeaf<'a>> {
337        self.materialize();
338
339        while self.components.len() <= i {
340            self.components.push(Vec::new());
341        }
342
343        &mut self.components[i]
344    }
345
346    /// Split the unsplit `raw` value into owned components so it can be edited
347    /// in place; a no-op once already split. Only edits pay this cost.
348    fn materialize(&mut self) {
349        let Some(raw) = self.raw.take() else {
350            return;
351        };
352
353        self.components = match raw {
354            Cow::Borrowed(bytes) => split_all(bytes),
355            Cow::Owned(bytes) => split_all_owned(&bytes),
356        };
357    }
358}
359
360impl fmt::Display for VcardValueNode<'_> {
361    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
362        if let Some(raw) = &self.raw {
363            return f.write_str(&String::from_utf8_lossy(raw));
364        }
365
366        for (i, component) in self.components.iter().enumerate() {
367            if i > 0 {
368                f.write_str(";")?;
369            }
370
371            for (j, leaf) in component.iter().enumerate() {
372                if j > 0 {
373                    f.write_str(",")?;
374                }
375
376                f.write_str(&String::from_utf8_lossy(leaf.as_bytes()))?;
377            }
378        }
379
380        Ok(())
381    }
382}
383
384/// One located component: a slice of the still-unsplit value, or its leaves
385/// once the value has been split for editing.
386enum Component<'s, 'a> {
387    /// The component's bytes, still `,`-joined, borrowed from the unsplit
388    /// value.
389    Raw(&'s [u8]),
390    /// The component's already-split leaves.
391    Split(&'s [VcardValueLeaf<'a>]),
392}
393
394/// The first `,`-separated value of a component (escape-aware): the bytes up to
395/// the first unescaped comma, or the whole component when it has none.
396fn first_value(component: &[u8]) -> &[u8] {
397    let mut first = None;
398    split_on(component, b',', |value| {
399        first.get_or_insert(value);
400    });
401    first.unwrap_or(component)
402}
403
404/// Split a value into its `;`-separated components, each a list of its
405/// `,`-separated leaves, borrowing from `bytes`.
406fn split_all(bytes: &[u8]) -> Vec<Vec<VcardValueLeaf<'_>>> {
407    let mut components = Vec::new();
408    split_on(bytes, b';', |component| {
409        let mut values = Vec::new();
410        split_on(component, b',', |value| {
411            values.push(VcardValueLeaf::from(value));
412        });
413        components.push(values);
414    });
415    components
416}
417
418/// Split like [`split_all`] but copying each leaf, for the owned bytes an
419/// edit-after-`into_static` value carries (which no slice can borrow from).
420fn split_all_owned(bytes: &[u8]) -> Vec<Vec<VcardValueLeaf<'static>>> {
421    let mut components = Vec::new();
422    split_on(bytes, b';', |component| {
423        let mut values = Vec::new();
424        split_on(component, b',', |value| {
425            values.push(VcardValueLeaf::from(value.to_vec()));
426        });
427        components.push(values);
428    });
429    components
430}
431
432/// Call `piece` for each span between unescaped `sep` bytes, always at least
433/// once. A backslash escapes the next byte (so `\;` / `\,` do not split), and
434/// `memchr` skips straight to the next `sep` or backslash instead of scanning
435/// byte by byte, so a large separator-free value (e.g. base64) is skipped in
436/// one pass.
437fn split_on<'b>(bytes: &'b [u8], sep: u8, mut piece: impl FnMut(&'b [u8])) {
438    let mut start = 0;
439    let mut i = 0;
440
441    while let Some(offset) = memchr::memchr2(b'\\', sep, &bytes[i..]) {
442        let pos = i + offset;
443        if bytes[pos] == b'\\' {
444            i = (pos + 2).min(bytes.len());
445        } else {
446            piece(&bytes[start..pos]);
447            start = pos + 1;
448            i = pos + 1;
449        }
450    }
451
452    piece(&bytes[start..]);
453}
454
455#[cfg(test)]
456mod tests {
457    use alloc::{borrow::Cow, string::ToString, vec};
458
459    use crate::tree::value::node::VcardValueNode;
460
461    #[test]
462    fn splits_components_and_values_then_round_trips() {
463        let node = VcardValueNode::parse(b"a;b,c;");
464        assert_eq!(node.component_count(), 3);
465        assert_eq!(
466            node.decode_at(1),
467            vec![Cow::Borrowed("b"), Cow::Borrowed("c")]
468        );
469        assert_eq!(node.to_string(), "a;b,c;");
470    }
471
472    #[test]
473    fn keeps_escaped_separators_inside_one_value() {
474        let node = VcardValueNode::parse(br"a\,b\;c;d");
475        assert_eq!(node.component_count(), 2);
476        assert_eq!(node.decode_at(0).len(), 1);
477        assert_eq!(node.to_string(), r"a\,b\;c;d");
478    }
479
480    #[test]
481    fn an_edit_splits_and_preserves_untouched_components() {
482        let mut node = VcardValueNode::parse(b"a;b;c");
483        node.set_at(1, &["X"]);
484        assert_eq!(node.to_string(), "a;X;c");
485    }
486
487    /// Every reader answers the same before and after the node materializes.
488    ///
489    /// A node holds its value as raw bytes until the first edit splits it into
490    /// components, and each reader has a branch per state. A parse-and-read
491    /// exercises the lazy branches; these are the other half, and a
492    /// disagreement between the two would surface as a value that changes
493    /// shape the moment an unrelated component is written.
494    fn assert_readers_agree(node: &VcardValueNode<'_>, components: usize) {
495        assert_eq!(node.component_count(), components);
496        assert_eq!(node.value_count(1), 2);
497        assert_eq!(node.decode_scalar_at(0), "a");
498        assert_eq!(
499            node.decode_at(1),
500            vec![Cow::Borrowed("b"), Cow::Borrowed("c")],
501        );
502        assert_eq!(node.decode_joined_at(1), "b,c");
503        assert_eq!(node.decode_bytes_at(2).as_ref(), b"d");
504    }
505
506    #[test]
507    fn readers_agree_before_and_after_an_edit_materializes_the_node() {
508        let mut node = VcardValueNode::parse(b"a;b,c;d");
509        assert_readers_agree(&node, 3);
510
511        // NOTE: Writing component 3 leaves the read components alone, but it is
512        // what moves the node off its raw bytes.
513        node.set_at(3, &["e"]);
514        assert_readers_agree(&node, 4);
515        assert_eq!(node.to_string(), "a;b,c;d;e");
516    }
517
518    #[test]
519    fn readers_agree_after_an_owned_node_is_edited() {
520        let mut node = VcardValueNode::parse(b"a;b,c;d").into_static();
521        assert_readers_agree(&node, 3);
522
523        node.set_at(3, &["e"]);
524        assert_readers_agree(&node, 4);
525        assert_eq!(node.to_string(), "a;b,c;d;e");
526    }
527}