| 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 | |
| 5 | use crate::{ |
| 6 | Error, ExGuid, |
| 7 | active::{ActivePage, Changes}, |
| 8 | document::{Kind, Revision}, |
| 9 | write::PropertyObject, |
| 10 | }; |
| 11 | use std::collections::BTreeMap; |
| 12 | |
| 13 | fn 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. |
| 18 | pub(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. |
| 36 | pub(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. |
| 40 | pub(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. |
| 69 | pub(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 | } |