Browse Source

Fix ToC generation for heading levels > 3 (bugfix) (#774)

* Fix ToC generation for heading levels > 3

* typo

* Add tests for deep ToCs

* Code style change
index-subcmd
southerntofu Vincent Prouillet 5 years ago
parent
commit
4695b029a0
1 changed files with 84 additions and 16 deletions
  1. +84
    -16
      components/rendering/src/table_of_contents.rs

+ 84
- 16
components/rendering/src/table_of_contents.rs View File

@@ -27,33 +27,58 @@ impl Default for Header {
} }
} }


// Takes a potential (mutable) parent and a header to try and insert into
// Returns true when it performed the insertion, false otherwise
fn insert_into_parent(potential_parent: Option<&mut Header>, header: &Header) -> bool {
match potential_parent {
None => {
// No potential parent to insert into so it needs to be insert higher
return false;
},
Some(parent) => {
let diff = header.level - parent.level;
if diff <= 0 {
// Heading is same level or higher so we don't insert here
return false;
}
if diff == 1 {
// We have a direct child of the parent
parent.children.push(header.clone());
return true;
}
// We need to go deeper
if !insert_into_parent(parent.children.iter_mut().last(), header) {
// No, we need to insert it here
parent.children.push(header.clone());
}
return true;
}
}
}

/// Converts the flat temp headers into a nested set of headers /// Converts the flat temp headers into a nested set of headers
/// representing the hierarchy /// representing the hierarchy
pub fn make_table_of_contents(headers: Vec<Header>) -> Vec<Header> { pub fn make_table_of_contents(headers: Vec<Header>) -> Vec<Header> {
let mut toc = vec![]; let mut toc = vec![];
'parent: for header in headers {
for header in headers {
if toc.is_empty() { if toc.is_empty() {
// First header, nothing to compare it with
toc.push(header); toc.push(header);
continue; continue;
} }


// See if we have to insert as a child of a previous header
for h in toc.iter_mut().rev() {
// Look in its children first
for child in h.children.iter_mut().rev() {
if header.level > child.level {
child.children.push(header);
continue 'parent;
}
}
if header.level > h.level {
h.children.push(header);
continue 'parent;
// We try to insert the current header in a previous one
match insert_into_parent(toc.iter_mut().last(), &header) {
true => {
// Header was successfully inserted as a child of a previous element
continue;
},
false => {
// Couldn't insert in a previous header, so it's a top-level header
toc.push(header);
continue;
} }
} }

// Nop, just insert it
toc.push(header)
} }


toc toc
@@ -91,6 +116,49 @@ mod tests {
assert_eq!(toc[1].children[0].children.len(), 2); assert_eq!(toc[1].children[0].children.len(), 2);
} }


#[test]
fn can_make_deep_toc() {
let input = vec![
Header::new(1),
Header::new(2),
Header::new(3),
Header::new(4),
Header::new(5),
Header::new(4),
];
let toc = make_table_of_contents(input);
assert_eq!(toc.len(), 1);
assert_eq!(toc[0].children.len(), 1);
assert_eq!(toc[0].children[0].children.len(), 1);
assert_eq!(toc[0].children[0].children[0].children.len(), 2);
assert_eq!(toc[0].children[0].children[0].children[0].children.len(), 1);
}

#[test]
fn can_make_deep_messy_toc() {
let input = vec![
Header::new(2), // toc[0]
Header::new(3),
Header::new(4),
Header::new(5),
Header::new(4),
Header::new(2), // toc[1]
Header::new(1), // toc[2]
Header::new(2),
Header::new(3),
Header::new(4),
];
let toc = make_table_of_contents(input);
assert_eq!(toc.len(), 3);
assert_eq!(toc[0].children.len(), 1);
assert_eq!(toc[0].children[0].children.len(), 2);
assert_eq!(toc[0].children[0].children[0].children.len(), 1);
assert_eq!(toc[1].children.len(), 0);
assert_eq!(toc[2].children.len(), 1);
assert_eq!(toc[2].children[0].children.len(), 1);
assert_eq!(toc[2].children[0].children[0].children.len(), 1);
}

#[test] #[test]
fn can_make_messy_toc() { fn can_make_messy_toc() {
let input = vec![ let input = vec![


Loading…
Cancel
Save