1//! Outline levels: OneNote stores a container's children at its child level and nests each
2//! run of deeper paragraphs in an outline group adding the rest
3//! (`evidence/structural-edits/xml/c6-first-tab.xml`, MS-ONE 2.2.22).
4
5use crate::{
6 Error, ExGuid,
7 active::{ActivePage, Changes},
8 document::{Kind, Revision},
9 write::PropertyObject,
10};
11use std::collections::BTreeMap;
12
13fn invalid(message: &'static str) -> Error {
14 Error { offset: 0, message }
15}
16
17/// How much deeper than `container` each of its children lies, groups lifted.
18pub(crate) fn stored_depths(view: &Revision<'_>, container: ExGuid) -> Vec<(ExGuid, u32)> {
19 fn walk(view: &Revision<'_>, id: ExGuid, base: u32, out: &mut Vec<(ExGuid, u32)>) {
20 let node = &view.nodes[&id];
21 let depth = base + u32::from(node.child_level.unwrap_or(0));
22 for child in &node.children {
23 match view.nodes[child].kind {
24 Kind::OutlineGroup => walk(view, *child, depth, out),
25 _ => out.push((*child, depth)),
26 }
27 }
28 }
29 let mut out = Vec::new();
30 walk(view, container, 0, &mut out);
31 out
32}
33
34/// Children in runs: one child at the container's child level, or a run sharing a deeper
35/// depth.
36pub(crate) type Runs = Vec<(u32, Vec<ExGuid>)>;
37
38/// The child level of a container whose children lie `depths` deeper than it, and its
39/// children in runs.
40pub(crate) fn runs(
41 children: &[ExGuid],
42 depths: &[u32],
43 is_cell: bool,
44) -> Result<(u32, Runs), Error> {
45 let level = *depths
46 .iter()
47 .min()
48 .ok_or_else(|| invalid("A container needs a child"))?;
49 if !(1..=31).contains(&level) || depths.iter().any(|depth| *depth > level + 31) {
50 return Err(invalid(
51 "Outline levels lie 1 to 31 deeper than their parent",
52 ));
53 }
54 if is_cell && depths.iter().any(|depth| *depth != level) {
55 return Err(invalid("Table cells hold no outline groups"));
56 }
57 let mut runs: Runs = Vec::new();
58 for (id, depth) in children.iter().zip(depths) {
59 match runs.last_mut() {
60 Some((last, ids)) if *last == *depth && *depth != level => ids.push(*id),
61 _ => runs.push((*depth, vec![*id])),
62 }
63 }
64 Ok((level, runs))
65}
66
67/// Stores `container`'s children as `runs` below child level `level`, each deeper run in a
68/// new outline group.
69pub(crate) fn regroup_changes(
70 active: &ActivePage<'_>,
71 container: ExGuid,
72 level: u32,
73 runs: &[(u32, Vec<ExGuid>)],
74) -> Result<Changes, Error> {
75 let parents = active.editable_parents(container)?;
76 let modified = crate::create::current_timestamps()?.0.to_le_bytes();
77 let raw = &active.live.revision;
78 let mut holder = PropertyObject::from_object(&raw.objects[&container])?;
79 let mut changed = BTreeMap::new();
80 let mut references = Vec::new();
81 for (depth, ids) in runs {
82 if *depth == level {
83 references.extend(holder.reference(ids[0])?);
84 continue;
85 }
86 let id = ExGuid {
87 guid: crate::write::fresh_guid()?,
88 n: 1,
89 };
90 let mut group = PropertyObject {
91 jcid: 0x60019,
92 bytes: crate::create::properties(&[
93 (0x14001d7a, modified.to_vec()),
94 (0x0c001c03, vec![(depth - level) as u8]),
95 ])?,
96 global_ids: std::sync::Arc::new(BTreeMap::from([(0, id.guid)])),
97 };
98 let mut members = Vec::new();
99 for member in ids {
100 members.extend(group.reference(*member)?);
101 }
102 group.set(&[(0x24001c20, &members)])?;
103 group.reference(id)?;
104 changed.insert(id, group);
105 references.extend(holder.reference(id)?);
106 }
107 holder.set(&[
108 (0x24001c20, &references),
109 (0x0c001c03, &[level as u8]),
110 (0x14001d7a, &modified),
111 ])?;
112 changed.insert(container, holder);
113 crate::formatting::touch_ancestors(raw, parents, container, &modified, &mut changed)?;
114 Ok(changed)
115}