Files
gridcraft/crates/model/src/cellmap.rs
T
vangel76andClaude Opus 5.5 8e5b1ac8e3 XLSX: write threaded comments and persons parts, read note visibility (#300)
- Writer: one xl/threadedComments/threadedCommentN.xml per sheet with
  comments (thread, replies via parentId, done for resolved, dT when known)
  and xl/persons/person.xml (one person per author), with the [MS-XLSX]
  relationship and content types. Each thread keeps its legacy note, linked
  by the tc={id} author and xr:uid; its body is the text plus replies.
  Ids are GUID-shaped and deterministic.
- Reader: keeps dT timestamps (Comment.dates, JSON-compatible), drops
  replies whose thread is missing, and reads note visibility from the VML
  <x:Visible/> (best effort, broken VML skipped silently).
- Invalid dates are never written. Round-trip and hostile-input tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 12:15:45 +02:00

53 lines
1.8 KiB
Rust

//! Serde helpers for maps keyed by `CellRef`. JSON object keys must be strings, so the keys are
//! written as `A1`-style text.
use std::collections::BTreeMap;
use gridcraft_core::CellRef;
use serde::de::Error;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub fn serialize<S: Serializer, V: Serialize>(map: &BTreeMap<CellRef, V>, s: S) -> Result<S::Ok, S::Error> {
s.collect_map(map.iter().map(|(k, v)| (k.a1(), v)))
}
pub fn deserialize<'de, D: Deserializer<'de>, V: Deserialize<'de>>(d: D) -> Result<BTreeMap<CellRef, V>, D::Error> {
let raw = BTreeMap::<String, V>::deserialize(d)?;
let mut out = BTreeMap::new();
for (k, v) in raw {
let cell = CellRef::parse(&k).ok_or_else(|| D::Error::custom(format!("invalid cell reference `{k}`")))?;
out.insert(cell, v);
}
Ok(out)
}
#[cfg(test)]
mod tests {
use crate::{Comment, Hyperlink, Sheet};
use gridcraft_core::CellRef;
#[test]
fn comments_and_hyperlinks_survive_json() {
let mut s = Sheet::new("S");
let a1 = CellRef::new(0, 0);
let c3 = CellRef::new(2, 2);
s.comments.insert(
a1,
Comment {
author: "Example".into(),
text: "note".into(),
replies: vec![("B".into(), "r".into())],
threaded: true,
resolved: false,
visible: false,
dates: vec![],
},
);
s.hyperlinks.insert(c3, Hyperlink { target: "Sheet1!A1".into(), tooltip: Some("jump".into()) });
let json = serde_json::to_string(&s).expect("serialize");
let back: Sheet = serde_json::from_str(&json).expect("deserialize");
assert_eq!(back.comments, s.comments);
assert_eq!(back.hyperlinks, s.hyperlinks);
}
}