use core::fmt;
use alloc::{borrow::Cow, string::String, vec::Vec};
use crate::tree::{
codec::{
encode::{encode_component, encode_leaf},
escape::escape_with,
mode::VcardEscaper,
unescape::{unescape_bytes, unescape_with},
},
leaf::VcardValueLeaf,
};
#[derive(Clone, Debug, Default)]
pub struct VcardValueNode<'a> {
raw: Option<Cow<'a, [u8]>>,
components: Vec<Vec<VcardValueLeaf<'a>>>,
pub escaper: VcardEscaper,
}
impl<'a> VcardValueNode<'a> {
pub fn parse(value: &'a [u8]) -> Self {
Self {
raw: Some(Cow::Borrowed(value)),
components: Vec::new(),
escaper: VcardEscaper::default(),
}
}
pub(crate) fn from_components(
components: Vec<Vec<VcardValueLeaf<'a>>>,
escaper: VcardEscaper,
) -> Self {
Self {
raw: None,
components,
escaper,
}
}
pub fn component_count(&self) -> usize {
match &self.raw {
Some(raw) => {
let mut count = 0;
split_on(raw, b';', |_| count += 1);
count
}
None => self.components.len(),
}
}
pub fn decode_at(&self, i: usize) -> Vec<Cow<'_, str>> {
match self.component_at(i) {
Some(Component::Raw(bytes)) => {
let mut values = Vec::new();
split_on(bytes, b',', |value| {
values.push(unescape_with(value, self.escaper))
});
values
}
Some(Component::Split(leaves)) => leaves
.iter()
.map(|leaf| unescape_with(leaf.as_bytes(), self.escaper))
.collect(),
None => Vec::new(),
}
}
pub fn decode_bytes_at(&self, i: usize) -> Cow<'_, [u8]> {
match self.component_at(i) {
Some(Component::Raw(bytes)) => unescape_bytes(first_value(bytes), self.escaper),
Some(Component::Split(leaves)) => leaves
.first()
.map(|leaf| unescape_bytes(leaf.as_bytes(), self.escaper))
.unwrap_or(Cow::Borrowed(b"")),
None => Cow::Borrowed(b""),
}
}
pub fn decode_scalar_at(&self, i: usize) -> Cow<'_, str> {
match self.component_at(i) {
Some(Component::Raw(bytes)) => unescape_with(first_value(bytes), self.escaper),
Some(Component::Split(leaves)) => leaves
.first()
.map(|leaf| unescape_with(leaf.as_bytes(), self.escaper))
.unwrap_or(Cow::Borrowed("")),
None => Cow::Borrowed(""),
}
}
pub fn decode_joined_at(&self, i: usize) -> Cow<'_, str> {
match self.component_at(i) {
Some(Component::Raw(bytes)) => unescape_with(bytes, self.escaper),
Some(Component::Split(leaves)) => {
if leaves.len() <= 1 {
return leaves
.first()
.map(|leaf| unescape_with(leaf.as_bytes(), self.escaper))
.unwrap_or(Cow::Borrowed(""));
}
let mut raw = Vec::new();
for (j, leaf) in leaves.iter().enumerate() {
if j > 0 {
raw.push(b',');
}
raw.extend_from_slice(leaf.as_bytes());
}
Cow::Owned(unescape_with(&raw, self.escaper).into_owned())
}
None => Cow::Borrowed(""),
}
}
pub(crate) fn first_value_bytes(&self) -> &[u8] {
match self.component_at(0) {
Some(Component::Raw(bytes)) => first_value(bytes),
Some(Component::Split(leaves)) => {
leaves.first().map(|leaf| leaf.as_bytes()).unwrap_or(b"")
}
None => b"",
}
}
pub fn set_at<S: AsRef<str>>(&mut self, i: usize, values: &[S]) {
self.materialize();
while self.components.len() <= i {
self.components.push(Vec::new());
}
self.components[i] = encode_component(values, self.escaper);
}
pub fn set_bytes_at<B: AsRef<[u8]>>(&mut self, i: usize, values: &[B]) {
self.materialize();
while self.components.len() <= i {
self.components.push(Vec::new());
}
self.components[i] = values
.iter()
.map(|v| VcardValueLeaf::from(escape_with(v.as_ref(), self.escaper).into_owned()))
.collect();
}
pub fn value_count(&self, i: usize) -> usize {
match self.component_at(i) {
Some(Component::Raw(bytes)) => {
let mut count = 0;
split_on(bytes, b',', |_| count += 1);
count
}
Some(Component::Split(leaves)) => leaves.len(),
None => 0,
}
}
pub fn set_value_at<S: AsRef<str>>(&mut self, i: usize, j: usize, value: S) {
let escaper = self.escaper;
let component = self.component_mut(i);
while component.len() <= j {
component.push(encode_leaf("", escaper));
}
component[j] = encode_leaf(value, escaper);
}
pub fn insert_value_at<S: AsRef<str>>(&mut self, i: usize, j: usize, value: S) {
let escaper = self.escaper;
let component = self.component_mut(i);
let at = j.min(component.len());
component.insert(at, encode_leaf(value, escaper));
}
pub fn push_value<S: AsRef<str>>(&mut self, i: usize, value: S) {
let escaper = self.escaper;
let component = self.component_mut(i);
component.push(encode_leaf(value, escaper));
}
pub fn remove_value_at(&mut self, i: usize, j: usize) {
self.materialize();
if let Some(component) = self.components.get_mut(i)
&& j < component.len()
{
component.remove(j);
}
}
pub(crate) fn write_bytes(&self, out: &mut Vec<u8>) {
if let Some(raw) = &self.raw {
out.extend_from_slice(raw);
return;
}
for (i, component) in self.components.iter().enumerate() {
if i > 0 {
out.push(b';');
}
for (j, leaf) in component.iter().enumerate() {
if j > 0 {
out.push(b',');
}
out.extend_from_slice(leaf.as_bytes());
}
}
}
pub(crate) fn into_static(self) -> VcardValueNode<'static> {
match self.raw {
Some(raw) => VcardValueNode {
raw: Some(Cow::Owned(raw.into_owned())),
components: Vec::new(),
escaper: self.escaper,
},
None => VcardValueNode {
raw: None,
components: self
.components
.into_iter()
.map(|component| {
component
.into_iter()
.map(VcardValueLeaf::into_static)
.collect()
})
.collect(),
escaper: self.escaper,
},
}
}
fn component_at(&self, i: usize) -> Option<Component<'_, 'a>> {
match &self.raw {
Some(raw) => {
let mut found = None;
let mut index = 0;
split_on(raw, b';', |component| {
if index == i {
found = Some(component);
}
index += 1;
});
found.map(Component::Raw)
}
None => self
.components
.get(i)
.map(|leaves| Component::Split(leaves)),
}
}
fn component_mut(&mut self, i: usize) -> &mut Vec<VcardValueLeaf<'a>> {
self.materialize();
while self.components.len() <= i {
self.components.push(Vec::new());
}
&mut self.components[i]
}
fn materialize(&mut self) {
let Some(raw) = self.raw.take() else {
return;
};
self.components = match raw {
Cow::Borrowed(bytes) => split_all(bytes),
Cow::Owned(bytes) => split_all_owned(&bytes),
};
}
}
impl fmt::Display for VcardValueNode<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(raw) = &self.raw {
return f.write_str(&String::from_utf8_lossy(raw));
}
for (i, component) in self.components.iter().enumerate() {
if i > 0 {
f.write_str(";")?;
}
for (j, leaf) in component.iter().enumerate() {
if j > 0 {
f.write_str(",")?;
}
f.write_str(&String::from_utf8_lossy(leaf.as_bytes()))?;
}
}
Ok(())
}
}
enum Component<'s, 'a> {
Raw(&'s [u8]),
Split(&'s [VcardValueLeaf<'a>]),
}
fn first_value(component: &[u8]) -> &[u8] {
let mut first = None;
split_on(component, b',', |value| {
first.get_or_insert(value);
});
first.unwrap_or(component)
}
fn split_all(bytes: &[u8]) -> Vec<Vec<VcardValueLeaf<'_>>> {
let mut components = Vec::new();
split_on(bytes, b';', |component| {
let mut values = Vec::new();
split_on(component, b',', |value| {
values.push(VcardValueLeaf::from(value));
});
components.push(values);
});
components
}
fn split_all_owned(bytes: &[u8]) -> Vec<Vec<VcardValueLeaf<'static>>> {
let mut components = Vec::new();
split_on(bytes, b';', |component| {
let mut values = Vec::new();
split_on(component, b',', |value| {
values.push(VcardValueLeaf::from(value.to_vec()));
});
components.push(values);
});
components
}
fn split_on<'b>(bytes: &'b [u8], sep: u8, mut piece: impl FnMut(&'b [u8])) {
let mut start = 0;
let mut i = 0;
while let Some(offset) = memchr::memchr2(b'\\', sep, &bytes[i..]) {
let pos = i + offset;
if bytes[pos] == b'\\' {
i = (pos + 2).min(bytes.len());
} else {
piece(&bytes[start..pos]);
start = pos + 1;
i = pos + 1;
}
}
piece(&bytes[start..]);
}
#[cfg(test)]
mod tests {
use alloc::{borrow::Cow, string::ToString, vec};
use crate::tree::value::node::VcardValueNode;
#[test]
fn splits_components_and_values_then_round_trips() {
let node = VcardValueNode::parse(b"a;b,c;");
assert_eq!(node.component_count(), 3);
assert_eq!(
node.decode_at(1),
vec![Cow::Borrowed("b"), Cow::Borrowed("c")]
);
assert_eq!(node.to_string(), "a;b,c;");
}
#[test]
fn keeps_escaped_separators_inside_one_value() {
let node = VcardValueNode::parse(br"a\,b\;c;d");
assert_eq!(node.component_count(), 2);
assert_eq!(node.decode_at(0).len(), 1);
assert_eq!(node.to_string(), r"a\,b\;c;d");
}
#[test]
fn an_edit_splits_and_preserves_untouched_components() {
let mut node = VcardValueNode::parse(b"a;b;c");
node.set_at(1, &["X"]);
assert_eq!(node.to_string(), "a;X;c");
}
fn assert_readers_agree(node: &VcardValueNode<'_>, components: usize) {
assert_eq!(node.component_count(), components);
assert_eq!(node.value_count(1), 2);
assert_eq!(node.decode_scalar_at(0), "a");
assert_eq!(
node.decode_at(1),
vec![Cow::Borrowed("b"), Cow::Borrowed("c")],
);
assert_eq!(node.decode_joined_at(1), "b,c");
assert_eq!(node.decode_bytes_at(2).as_ref(), b"d");
}
#[test]
fn readers_agree_before_and_after_an_edit_materializes_the_node() {
let mut node = VcardValueNode::parse(b"a;b,c;d");
assert_readers_agree(&node, 3);
node.set_at(3, &["e"]);
assert_readers_agree(&node, 4);
assert_eq!(node.to_string(), "a;b,c;d;e");
}
#[test]
fn readers_agree_after_an_owned_node_is_edited() {
let mut node = VcardValueNode::parse(b"a;b,c;d").into_static();
assert_readers_agree(&node, 3);
node.set_at(3, &["e"]);
assert_readers_agree(&node, 4);
assert_eq!(node.to_string(), "a;b,c;d;e");
}
}