Display an outline as a nested table: Difference between revisions

From Rosetta Code
Content added Content deleted
(Added a Python implementation)
Line 934: Line 934:
style = "background: #ddffdd;" >(The sum of the widths of its children)</td>
style = "background: #ddffdd;" >(The sum of the widths of its children)</td>
</tr>
</tr>
</table>

=={{header|Nim}}==

<lang Nim>import strutils

const Outline = """Display an outline as a nested table.
Parse the outline to a tree,
measuring the indent of each line,
translating the indentation to a nested structure,
and padding the tree to even depth.
count the leaves descending from each node,
defining the width of a leaf as 1,
and the width of a parent node as a sum.
(The sum of the widths of its children)
and write out a table with 'colspan' values
either as a wiki table,
or as HTML."""

type Color {.pure.} = enum
NoColor
Yellow = "#ffffe6;"
Orange = "#ffebd2;"
Green = "#f0fff0;"
Blue = "#e6ffff;"

const Line1Color = Yellow
const Line2Colors = [Orange, Green, Blue]

type Node = ref object
value: string
level: Natural
width: Natural
color: Color
parent: Node
children: seq[Node]

#---------------------------------------------------------------------------------------------------

proc leadingSpaces(line: string): int =
## return the number of leading spaces.
while line[result] == ' ':
inc result

#---------------------------------------------------------------------------------------------------

proc buildTree(outline: string): tuple[root: Node, depth: Natural] =
## Build the tree for the given outline.

result.root = Node()
var level: int
var startPos = @[-1]
var nodes: seq[Node] = @[result.root]
var linecount = 0

for line in Outline.splitLines:
inc linecount
if line.len == 0: continue
let start = line.leadingSpaces()
level = startPos.find(start)

if level < 0:
# Level not yet encountered.
if start < startPos[^1]:
raise newException(ValueError, "wrong indentation at line " & $linecount)
startPos.add(start)
nodes.add(nil)
level = startPos.high

# Create the node.
let node = Node(value: line.strip(), level: level)
let parent = nodes[level - 1]
parent.children.add(node)
node.parent = parent
nodes[level] = node # Set the node as current node for this level.

result.depth = nodes.high

#---------------------------------------------------------------------------------------------------

proc padTree(node: Node; depth: Natural) =
## pad the tree with empty nodes to get an even depth.
if node.level == depth:
return
if node.children.len == 0:
# Add an empty node.
node.children.add(Node(level: node.level + 1, parent: node))
for child in node.children:
child.padTree(depth)

#---------------------------------------------------------------------------------------------------

proc computeWidths(node: Node) =
## Compute the widths.
var width = 0
if node.children.len == 0:
width = 1
else:
for child in node.children:
child.computeWidths()
inc width, child.width
node.width = width

#---------------------------------------------------------------------------------------------------

proc build(nodelists: var seq[seq[Node]]; node: Node) =
## Build the list of nodes per level.
nodelists[node.level].add(node)
for child in node.children:
nodelists.build(child)

#---------------------------------------------------------------------------------------------------

proc setColors(nodelists: seq[seq[Node]]) =
## Set the colors of the nodes.
for node in nodelists[1]:
node.color = Line1Color
for i, node in nodelists[2]:
node.color = Line2Colors[i mod Line2Colors.len]
for level in 3..nodelists.high:
for node in nodelists[level]:
node.color = if node.value.len != 0: node.parent.color else: NoColor

#---------------------------------------------------------------------------------------------------

proc writeWikiTable(nodelists: seq[seq[Node]]) =
## Output the wikitable.
echo "{| class='wikitable' style='text-align: center;'"
for level in 1..nodelists.high:
echo "|-"
for node in nodelists[level]:
if node.width > 1:
# Node with children.
echo "| style='background: $1 ' colspan=$2 | $3".format(node.color, node.width, node.value)
elif node.value.len > 0:
# Leaf with contents.
echo "| style='background: $1 ' | $2".format(node.color, node.value)
else:
# Empty cell.
echo "| | "
echo "|}"

#---------------------------------------------------------------------------------------------------

proc writeHtml(nodelists: seq[seq[Node]]) =
## Output the HTML.
echo "<table class='wikitable' style='text-align: center;'>"
for level in 1..nodelists.high:
echo " <tr>"
for node in nodelists[level]:
if node.width > 1:
# Node with children.
echo " <td colspan='$1' style='background-color: $2'>$3</td>".format(node.width, node.color, node.value)
elif node.value.len > 0:
# Leaf with contents.
echo " <td style='background-color: $1'>$2</td>".format(node.color, node.value)
else:
# Empty cell.
echo " <td></td>"
echo " </tr>"
echo "</table>"

#———————————————————————————————————————————————————————————————————————————————————————————————————

let (root, depth) = Outline.buildTree()
root.padTree(depth)
root.computeWidths()
var nodelists = newSeq[seq[Node]](depth + 1)
nodelists.build(root)
nodelists.setColors()
nodelists.writeWikiTable()
nodelists.writeHtml()</lang>

{{out}}
WikiTable:
{| class='wikitable' style='text-align: center;'
|-
| style='background: #ffffe6; ' colspan=7 | Display an outline as a nested table.
|-
| style='background: #ffebd2; ' colspan=3 | Parse the outline to a tree,
| style='background: #f0fff0; ' colspan=2 | count the leaves descending from each node,
| style='background: #e6ffff; ' colspan=2 | and write out a table with 'colspan' values
|-
| style='background: #ffebd2; ' | measuring the indent of each line,
| style='background: #ffebd2; ' | translating the indentation to a nested structure,
| style='background: #ffebd2; ' | and padding the tree to even depth.
| style='background: #f0fff0; ' | defining the width of a leaf as 1,
| style='background: #f0fff0; ' | and the width of a parent node as a sum.
| style='background: #e6ffff; ' | either as a wiki table,
| style='background: #e6ffff; ' | or as HTML.
|-
| |
| |
| |
| |
| style='background: #f0fff0; ' | (The sum of the widths of its children)
| |
| |
|}
HTML:
<table class='wikitable' style='text-align: center;'>
<tr>
<td colspan='7' style='background-color: #ffffe6;'>Display an outline as a nested table.</td>
</tr>
<tr>
<td colspan='3' style='background-color: #ffebd2;'>Parse the outline to a tree,</td>
<td colspan='2' style='background-color: #f0fff0;'>count the leaves descending from each node,</td>
<td colspan='2' style='background-color: #e6ffff;'>and write out a table with 'colspan' values</td>
</tr>
<tr>
<td style='background-color: #ffebd2;'>measuring the indent of each line,</td>
<td style='background-color: #ffebd2;'>translating the indentation to a nested structure,</td>
<td style='background-color: #ffebd2;'>and padding the tree to even depth.</td>
<td style='background-color: #f0fff0;'>defining the width of a leaf as 1,</td>
<td style='background-color: #f0fff0;'>and the width of a parent node as a sum.</td>
<td style='background-color: #e6ffff;'>either as a wiki table,</td>
<td style='background-color: #e6ffff;'>or as HTML.</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td style='background-color: #f0fff0;'>(The sum of the widths of its children)</td>
<td></td>
<td></td>
</tr>
</table>
</table>



Revision as of 10:59, 23 November 2020

Task
Display an outline as a nested table
You are encouraged to solve this task according to the task description, using any language you may know.
Display an outline as a nested table.
Parse the outline to a tree, count the leaves descending from each node, and write out a table with 'colspan' values
measuring the indent of each line, translating the indentation to a nested structure, and padding the tree to even depth. defining the width of a leaf as 1, and the width of a parent node as a sum. either as a wiki table, or as HTML.
(The sum of the widths of its children)

The graphic representation of outlines is a staple of mind-mapping and the planning of papers, reports, and speeches.

Task

Given a outline with at least 3 levels of indentation, for example:

Display an outline as a nested table.
    Parse the outline to a tree,
        measuring the indent of each line,
        translating the indentation to a nested structure,
        and padding the tree to even depth.
    count the leaves descending from each node,
        defining the width of a leaf as 1,
        and the width of a parent node as a sum.
            (The sum of the widths of its children)
    and write out a table with 'colspan' values
        either as a wiki table,
        or as HTML.

write a program in your language which translates your outline into a nested table, with WikiTable or HTML colspan values attached (where needed) to parent nodes in the nested table.

The WikiTable at the top of this page was generated from the indented outline shown above, producing the following markup string:

{| class="wikitable" style="text-align: center;"
|-
| style="background: #ffffe6; " colspan=7 | Display an outline as a nested table.
|-
| style="background: #ffebd2; " colspan=3 | Parse the outline to a tree,
| style="background: #f0fff0; " colspan=2 | count the leaves descending from each node,
| style="background: #e6ffff; " colspan=2 | and write out a table with 'colspan' values
|-
| style="background: #ffebd2; " | measuring the indent of each line,
| style="background: #ffebd2; " | translating the indentation to a nested structure,
| style="background: #ffebd2; " | and padding the tree to even depth.
| style="background: #f0fff0; " | defining the width of a leaf as 1,
| style="background: #f0fff0; " | and the width of a parent node as a sum.
| style="background: #e6ffff; " | either as a wiki table,
| style="background: #e6ffff; " | or as HTML.
|-
|  | 
|  | 
|  | 
|  | 
| style="background: #f0fff0; " | (The sum of the widths of its children)
|  | 
|  | 
|}
Extra credit

Use background color to distinguish the main stages of your outline, so that the subtree of each node at level two is consistently colored, and the edges between adjacent subtrees are immediately revealed.

Output

Display your nested table on this page.

Go

<lang go>package main

import (

   "fmt"
   "strings"

)

type nNode struct {

   name     string
   children []nNode

}

type iNode struct {

   level int
   name  string

}

func toNest(iNodes []iNode, start, level int, n *nNode) {

   if level == 0 {
       n.name = iNodes[0].name
   }
   for i := start + 1; i < len(iNodes); i++ {
       if iNodes[i].level == level+1 {
           c := nNode{iNodes[i].name, nil}
           toNest(iNodes, i, level+1, &c)
           n.children = append(n.children, c)
       } else if iNodes[i].level <= level {
           return
       }
   }

}

func makeIndent(outline string, tab int) []iNode {

   lines := strings.Split(outline, "\n")
   iNodes := make([]iNode, len(lines))
   for i, line := range lines {
       line2 := strings.TrimLeft(line, " ")
       le, le2 := len(line), len(line2)
       level := (le - le2) / tab
       iNodes[i] = iNode{level, line2}
   }
   return iNodes

}

func toMarkup(n nNode, cols []string, depth int) string {

   var span int
   var colSpan func(nn nNode)
   colSpan = func(nn nNode) {
       for i, c := range nn.children {
           if i > 0 {
               span++
           }
           colSpan(c)
       }
   }
   for _, c := range n.children {
       span = 1
       colSpan(c)
   }
   var lines []string
   lines = append(lines, `{| class="wikitable" style="text-align: center;"`)
   const l1, l2 = "|-", "|  |"
   lines = append(lines, l1)
   span = 1
   colSpan(n)
   s := fmt.Sprintf(`| style="background: %s " colSpan=%d | %s`, cols[0], span, n.name)
   lines = append(lines, s, l1)
   var nestedFor func(nn nNode, level, maxLevel, col int)
   nestedFor = func(nn nNode, level, maxLevel, col int) {
       if level == 1 && maxLevel > level {
           for i, c := range nn.children {
               nestedFor(c, 2, maxLevel, i)
           }
       } else if level < maxLevel {
           for _, c := range nn.children {
               nestedFor(c, level+1, maxLevel, col)
           }
       } else {
           if len(nn.children) > 0 {
               for i, c := range nn.children {
                   span = 1
                   colSpan(c)
                   cn := col + 1
                   if maxLevel == 1 {
                       cn = i + 1
                   }
                   s := fmt.Sprintf(`| style="background: %s " colspan=%d | %s`, cols[cn], span, c.name)
                   lines = append(lines, s)
               }
           } else {
               lines = append(lines, l2)
           }
       }
   }
   for maxLevel := 1; maxLevel < depth; maxLevel++ {
       nestedFor(n, 1, maxLevel, 0)
       if maxLevel < depth-1 {
           lines = append(lines, l1)
       }
   }
   lines = append(lines, "|}")
   return strings.Join(lines, "\n")

}

func main() {

   const outline = `Display an outline as a nested table.
   Parse the outline to a tree,
       measuring the indent of each line,
       translating the indentation to a nested structure,
       and padding the tree to even depth.
   count the leaves descending from each node,
       defining the width of a leaf as 1,
       and the width of a parent node as a sum.
           (The sum of the widths of its children) 
   and write out a table with 'colspan' values
       either as a wiki table,
       or as HTML.`
   const (
       yellow = "#ffffe6;"
       orange = "#ffebd2;"
       green  = "#f0fff0;"
       blue   = "#e6ffff;"
       pink   = "#ffeeff;"
   )
   cols := []string{yellow, orange, green, blue, pink}
   iNodes := makeIndent(outline, 4)
   var n nNode
   toNest(iNodes, 0, 0, &n)
   fmt.Println(toMarkup(n, cols, 4))
   fmt.Println("\n")
   const outline2 = `Display an outline as a nested table.
   Parse the outline to a tree,
       measuring the indent of each line,
       translating the indentation to a nested structure,
       and padding the tree to even depth.
   count the leaves descending from each node,
       defining the width of a leaf as 1,
       and the width of a parent node as a sum.
           (The sum of the widths of its children)
           Propagating the sums upward as necessary. 
   and write out a table with 'colspan' values
       either as a wiki table,
       or as HTML.
   Optionally add color to the nodes.`
   cols2 := []string{blue, yellow, orange, green, pink}
   var n2 nNode
   iNodes2 := makeIndent(outline2, 4)
   toNest(iNodes2, 0, 0, &n2)
   fmt.Println(toMarkup(n2, cols2, 4))

}</lang>

Output:
Display an outline as a nested table.
Parse the outline to a tree, count the leaves descending from each node, and write out a table with 'colspan' values
measuring the indent of each line, translating the indentation to a nested structure, and padding the tree to even depth. defining the width of a leaf as 1, and the width of a parent node as a sum. either as a wiki table, or as HTML.
(The sum of the widths of its children)


Display an outline as a nested table.
Parse the outline to a tree, count the leaves descending from each node, and write out a table with 'colspan' values Optionally add color to the nodes.
measuring the indent of each line, translating the indentation to a nested structure, and padding the tree to even depth. defining the width of a leaf as 1, and the width of a parent node as a sum. either as a wiki table, or as HTML.
(The sum of the widths of its children) Propagating the sums upward as necessary.

JavaScript

<lang javascript>(() => {

   'use strict';
   // main :: IO ()
   const main = () => {
       const outline = `Display an outline as a nested table.
   Parse the outline to a tree,
       measuring the indent of each line,
       translating the indentation to a nested structure,
       and padding the tree to even depth.
   count the leaves descending from each node,
       defining the width of a leaf as 1,
       and the width of a parent node as a sum.
           (The sum of the widths of its children)
   and write out a table with 'colspan' values
       either as a wiki table,
       or as HTML.`
       console.log(
           wikiTableFromForest(
               forestOfEvenDepth(
                   map(compose(
                       coloredKeyLines(Object.keys(dictColors)),
                       paintedTree('backColor')('yellow'),
                       measuredTree
                   ))(
                       forestFromOutline(outline)
                   )
               )
           )
       );
   };
   // TRANSLATION OF OUTLINE TO NESTED TABLE -----------------
   // forestFromOutline :: String -> [Tree String]
   const forestFromOutline = s =>
       // A list of trees, derived from an
       // indented outline text.
       forestFromLineIndents(
           indentLevelsFromLines(lines(s))
       );
   // wikiTableFromForest :: [Tree (String, Dict)] -> String
   const wikiTableFromForest = forest => {
       // Lines of wiki markup representing a nested tree
       // with 'colspan' values for parent nodes,
       // and varying background colors.
       const tableRows = trees => {
           const rows = tail(levels(Node('virtual')(trees)));
           return unlines(concatMap(row =>
               cons('|-')(
                   map(cell => {
                       const
                           dct = cell[1],
                           color = dct.backColor,
                           width = dct.leafSum;
                       return '| ' + (
                           Boolean(color) ? (
                               'style="background: ' +
                               dictColors[color] + '; "'
                           ) : 
                       ) + (
                           1 < width ? (
                               ' colspan=' + str(width)
                           ) : 
                       ) + ' | ' + cell[0];
                   })(row)
               )
           )(rows));
       };
       return '{| class="wikitable" style="text-align: center;"\n' +
           tableRows(forest) +
           '\n|}'
   };
   // indentLevelsFromLines :: [String] -> [(Int, String)]
   const indentLevelsFromLines = xs => {
       // A list of (indentLevel, Text) tuples.
       const
           pairs = xs.map(compose(
               firstArrow(length), span(isSpace)
           )),
           indentUnit = maybe(1)(fst)(
               find(x => 0 < x[0])(pairs)
           )
       return pairs.map(
           firstArrow(flip(div)(indentUnit))
       );
   };
   // forestFromLineIndents :: [(Int, String)] -> [Tree String]
   const forestFromLineIndents = tuples => {
       // A list of nested trees derived from
       // a list of indented lines.
       const go = xs =>
           0 < xs.length ? (() => {
               const [n, s] = Array.from(xs[0]);
               // Lines indented under this line,
               // tupled with all the rest.
               const [firstTreeLines, rest] = Array.from(
                   span(x => n < x[0])(xs.slice(1))
               );
               // This first tree, and then the rest.
               return [Node(s)(go(firstTreeLines))]
                   .concat(go(rest));
           })() : [];
       return go(tuples);
   };
   // forestOfEvenDepth :: [Tree (a, Dict)] -> [Tree (a, Dict)]
   const forestOfEvenDepth = measuredTrees => {
       // A tree padded downwards so that every branch
       // descends to the same depth.
       const go = n => tree =>
           1 >= n ? (
               tree
           ) : Node(tree.root)(
               0 < tree.nest.length ? (
                   tree.nest.map(go(n - 1))
               ) : [Node(Tuple()({}))([])]
           );
       return measuredTrees.map(go(
           1 + maximumBy(x => root(x)[1].layerSum)(
               measuredTrees
           ).root[1].layerSum
       ));
   };
   // BACKGROUND COLOURS FOR SECTIONS OF THE TREE ----
   // coloredKeyLines :: [String] ->
   // Tree (String, Dict) -> Tree (String, Dict)
   const coloredKeyLines = colorNames => node =>
       Node(root(node))(
           zipWith(paintedTree('backColor'))(
               take(node.nest.length)(
                   cycle(tail(colorNames))
               )
           )(node.nest)
       );
   // paintedTree :: String -> a -> Tree (b, dict) -> Tree (b, dict)
   const paintedTree = k => v => node => {
       const go = x =>
           Node(Tuple(root(x)[0])(
               insertDict(k)(v)(root(x)[1])
           ))(nest(x).map(go));
       return go(node);
   };
   // dictColors :: Dict
   const dictColors = {
       yellow: '#ffffe6',
       orange: '#ffebd2',
       green: '#f0fff0',
       blue: '#e6ffff',
       pink: '#ffeeff',
       gray: 
   };


   // GENERIC FUNCTIONS ----------------------------
   // Just :: a -> Maybe a
   const Just = x => ({
       type: 'Maybe',
       Nothing: false,
       Just: x
   });
   // Node :: a -> [Tree a] -> Tree a
   const Node = v => xs => ({
       type: 'Node',
       root: v, // any type of value (consistent across tree)
       nest: xs || []
   });
   // Nothing :: Maybe a
   const Nothing = () => ({
       type: 'Maybe',
       Nothing: true,
   });
   // Tuple (,) :: a -> b -> (a, b)
   const Tuple = a => b => ({
       type: 'Tuple',
       '0': a,
       '1': b,
       length: 2
   });
   // compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
   const compose = (...fs) =>
       x => fs.reduceRight((a, f) => f(a), x);
   // concatMap :: (a -> [b]) -> [a] -> [b]
   const concatMap = f => xs =>
       xs.flatMap(f);
   // cons :: a -> [a] -> [a]
   const cons = x => xs =>
       Array.isArray(xs) ? (
           [x].concat(xs)
       ) : 'GeneratorFunction' !== xs.constructor.constructor.name ? (
           x + xs
       ) : ( // Existing generator wrapped with one additional element
           function*() {
               yield x;
               let nxt = xs.next()
               while (!nxt.done) {
                   yield nxt.value;
                   nxt = xs.next();
               }
           }
       )();
   // cycle :: [a] -> Generator [a]
   function* cycle(xs) {
       const lng = xs.length;
       let i = 0;
       while (true) {
           yield(xs[i])
           i = (1 + i) % lng;
       }
   }
   // div :: Int -> Int -> Int
   const div = x => y => Math.floor(x / y);


   // find :: (a -> Bool) -> [a] -> Maybe a
   const find = p => xs => {
       const i = xs.findIndex(p);
       return -1 !== i ? (
           Just(xs[i])
       ) : Nothing();
   };


   // firstArrow :: (a -> b) -> ((a, c) -> (b, c))
   const firstArrow = f =>
       // A simple function lifted to one which applies
       // to a tuple, transforming only its first item.
       xy => Tuple(f(xy[0]))(
           xy[1]
       );
   // flip :: (a -> b -> c) -> b -> a -> c
   const flip = f =>
       1 < f.length ? (
           (a, b) => f(b, a)
       ) : (x => y => f(y)(x));
   // foldTree :: (a -> [b] -> b) -> Tree a -> b
   const foldTree = f => tree => {
       const go = node => f(node.root)(
           node.nest.map(go)
       );
       return go(tree);
   };
   // foldl1 :: (a -> a -> a) -> [a] -> a
   const foldl1 = f => xs =>
       1 < xs.length ? xs.slice(1)
       .reduce(uncurry(f), xs[0]) : xs[0];
   // fromEnum :: Enum a => a -> Int
   const fromEnum = x =>
       typeof x !== 'string' ? (
           x.constructor === Object ? (
               x.value
           ) : parseInt(Number(x))
       ) : x.codePointAt(0);
   // fst :: (a, b) -> a
   const fst = tpl => tpl[0];
   // gt :: Ord a => a -> a -> Bool
   const gt = x => y =>
       'Tuple' === x.type ? (
           x[0] > y[0]
       ) : (x > y);
   // insertDict :: String -> a -> Dict -> Dict
   const insertDict = k => v => dct =>
       Object.assign({}, dct, {
           [k]: v
       });
   // isSpace :: Char -> Bool
   const isSpace = c => /\s/.test(c);
   // Returns Infinity over objects without finite length.
   // This enables zip and zipWith to choose the shorter
   // argument when one is non-finite, like cycle, repeat etc
   // length :: [a] -> Int
   const length = xs =>
       (Array.isArray(xs) || 'string' === typeof xs) ? (
           xs.length
       ) : Infinity;
   // levels :: Tree a -> a
   const levels = tree => {
       const xs = [
           [root(tree)]
       ];
       let level = [tree].flatMap(nest);
       while (0 < level.length) {
           xs.push(level.map(root));
           level = level.flatMap(nest);
       }
       return xs;
   };
   // lines :: String -> [String]
   const lines = s => s.split(/[\r\n]/);
   // maybe :: b -> (a -> b) -> Maybe a -> b
   const maybe = v =>
       // Default value (v) if m is Nothing, or f(m.Just)
       f => m => m.Nothing ? v : f(m.Just);
   // map :: (a -> b) -> [a] -> [b]
   const map = f => xs =>
       (Array.isArray(xs) ? (
           xs
       ) : xs.split()).map(f);
   // max :: Ord a => a -> a -> a
   const max = a => b => gt(b)(a) ? b : a;
   // maximumBy :: (a -> a -> Ordering) -> [a] -> a
   const maximumBy = f => xs =>
       0 < xs.length ? (
           xs.slice(1)
           .reduce((a, x) => 0 < f(x)(a) ? x : a, xs[0])
       ) : undefined;
   // measuredTree :: Tree a -> Tree (a, (Int, Int, Int))
   const measuredTree = tree => {
       // A tree in which each node is tupled with
       // a (leafSum, layerSum, nodeSum) measure of its sub-tree,
       // where leafSum is the number of descendant leaves,
       // and layerSum is the number of descendant levels,
       // and nodeSum counts all nodes, including the root.
       // Index is a position in a zero-based top-down
       // left to right series.
       // For additional parent indices, see parentIndexedTree.
       const whni = (w, h, n, i) => ({
           leafSum: w,
           layerSum: h,
           nodeSum: n,
           index: i
       });
       let i = 0;
       return foldTree(
           x => {
               let topDown = i++;
               return xs => Node(
                   Tuple(x)(
                       0 < xs.length ? (() => {
                           const dct = xs.reduce(
                               (a, node) => {
                                   const dimns = node.root[1];
                                   return whni(
                                       a.leafSum + dimns.leafSum,
                                       max(a.layerSum)(
                                           dimns.layerSum
                                       ),
                                       a.nodeSum + dimns.nodeSum,
                                       topDown
                                   );
                               }, whni(0, 0, 0, topDown)
                           );
                           return whni(
                               dct.leafSum,
                               1 + dct.layerSum,
                               1 + dct.nodeSum,
                               topDown
                           );
                       })() : whni(1, 0, 1, topDown)
                   )
               )(xs);
           }
       )(tree);
   };
   // nest :: Tree a -> [a]
   const nest = tree => tree.nest;
   // root :: Tree a -> a
   const root = tree => tree.root;
   // snd :: (a, b) -> b
   const snd = tpl => tpl[1];
   // span :: (a -> Bool) -> [a] -> ([a], [a])
   const span = p => xs => {
       const iLast = xs.length - 1;
       return splitAt(
           until(i => iLast < i || !p(xs[i]))(
               succ
           )(0)
       )(xs);
   };
   // splitAt :: Int -> [a] -> ([a], [a])
   const splitAt = n => xs =>
       Tuple(xs.slice(0, n))(
           xs.slice(n)
       );
   // str :: a -> String
   const str = x => x.toString();
   // succ :: Enum a => a -> a
   const succ = x => 1 + x;
   // tail :: [a] -> [a]
   const tail = xs => 0 < xs.length ? xs.slice(1) : [];
   // take :: Int -> [a] -> [a]
   // take :: Int -> String -> String
   const take = n => xs =>
       'GeneratorFunction' !== xs.constructor.constructor.name ? (
           xs.slice(0, n)
       ) : [].concat.apply([], Array.from({
           length: n
       }, () => {
           const x = xs.next();
           return x.done ? [] : [x.value];
       }));
   // uncurry :: (a -> b -> c) -> ((a, b) -> c)
   const uncurry = f =>
       function() {
           const
               args = Array.from(arguments),
               a = 1 < args.length ? (
                   args
               ) : args[0]; // Tuple object.
           return f(a[0])(a[1]);
       };
   // unlines :: [String] -> String
   const unlines = xs => xs.join('\n');
   // until :: (a -> Bool) -> (a -> a) -> a -> a
   const until = p => f => x => {
       let v = x;
       while (!p(v)) v = f(v);
       return v;
   };
   // zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
   const zipWith = f => xs => ys =>
       xs.slice(
           0, Math.min(xs.length, ys.length)
       ).map((x, i) => f(x)(ys[i]));
   // MAIN ---
   return main();

})();</lang>

Output:
Display an outline as a nested table.
Parse the outline to a tree, count the leaves descending from each node, and write out a table with 'colspan' values
measuring the indent of each line, translating the indentation to a nested structure, and padding the tree to even depth. defining the width of a leaf as 1, and the width of a parent node as a sum. either as a wiki table, or as HTML.
(The sum of the widths of its children)

Julia

<lang julia>using DataFrames

text = """ Display an outline as a nested table.

   Parse the outline to a tree,
       measuring the indent of each line,
       translating the indentation to a nested structure,
       and padding the tree to even depth.
   count the leaves descending from each node,
       defining the width of a leaf as 1,
       and the width of a parent node as a sum.
           (The sum of the widths of its children)
   and write out a table with 'colspan' values
       either as a wiki table,
       or as HTML.

"""

const bcolor = ["background: #ffffaa;", "background: #ffdddd;",

   "background: #ddffdd;", "background: #ddddff;"]

colorstring(n) = bcolor[n == 1 ? 1  : mod1(n - 1, length(bcolor) - 1) + 1]

function processtable(txt)

   df = DataFrame()
   indents = Int[]
   linetext = String[]
   for line in split(txt, "\n")
       if length(line) > 0
           n = findfirst(!isspace, line)
           push!(linetext, String(line[n:end]))
           push!(indents, n - 1)
       end
   end
   len = length(indents)
   divisor = gcd(indents)
   indents .= div.(indents, divisor)
   parent(i) = (n = findlast(x -> indents[x] < indents[i], 1:i-1)) == nothing ? 0 : n
   children(i) = findall(x -> parent(x) == i, 1:len)
   treesize(i) = (s = children(i); isempty(s) ? 1 : sum(treesize, s))
   prioronlevel(i) = (j = indents[i]; filter(x -> indents[x] == j, 1:i-1))
   treesizeprior(i) = (s = prioronlevel(i); isempty(s) ? 0 : sum(treesize, s))
   startpos(i) = (n = parent(i)) == 0 ? 0 : treesizeprior(n) - treesizeprior(i)
   function leveloneparent(i)
       p = parent(i)
       return p < 1 ? 1 : p ==1 ? sum(x -> indents[x] <= 1, 1:i) : leveloneparent(p)
   end
   df.TEXT = linetext
   df.INDENT = indents
   df.COLSPAN = [treesize(i) for i in 1:len]
   df.PRESPAN = [max(0, startpos(i)) for i in 1:len]
   df.LEVELONEPARENT = [leveloneparent(i) for i in 1:len]
   return df

end

function htmlfromdataframe(df)

println("

A Rosetta Code Nested Table

")
   for ind in minimum(df.INDENT):maximum(df.INDENT)
println("") for row in eachrow(df) if row[:INDENT] == ind if row[:PRESPAN] > 0 println("")
               end
print("") end end println("") end println("
0
                   println("colspan=\"$(row[:COLSPAN])\"")
               end
println(" style = \"$(colorstring(row[:LEVELONEPARENT]))\" >$(row[:TEXT])

")

end

htmlfromdataframe(processtable(text)) textplus = text * " Optionally add color to the nodes." htmlfromdataframe(processtable(textplus))

</lang>

Output:

A Rosetta Code Nested Table

Display an outline as a nested table.
Parse the outline to a tree, count the leaves descending from each node, and write out a table with 'colspan' values
measuring the indent of each line, translating the indentation to a nested structure, and padding the tree to even depth. defining the width of a leaf as 1, and the width of a parent node as a sum. either as a wiki table, or as HTML.
(The sum of the widths of its children)

A Rosetta Code Nested Table

Display an outline as a nested table.
Parse the outline to a tree, count the leaves descending from each node, and write out a table with 'colspan' values Optionally add color to the nodes.
measuring the indent of each line, translating the indentation to a nested structure, and padding the tree to even depth. defining the width of a leaf as 1, and the width of a parent node as a sum. either as a wiki table, or as HTML.
(The sum of the widths of its children)

Nim

<lang Nim>import strutils

const Outline = """Display an outline as a nested table.

   Parse the outline to a tree,
       measuring the indent of each line,
       translating the indentation to a nested structure,
       and padding the tree to even depth.
   count the leaves descending from each node,
       defining the width of a leaf as 1,
       and the width of a parent node as a sum.
           (The sum of the widths of its children)
   and write out a table with 'colspan' values
       either as a wiki table,
       or as HTML."""

type Color {.pure.} = enum

 NoColor
 Yellow = "#ffffe6;"
 Orange = "#ffebd2;"
 Green = "#f0fff0;"
 Blue = "#e6ffff;"

const Line1Color = Yellow const Line2Colors = [Orange, Green, Blue]

type Node = ref object

 value: string
 level: Natural
 width: Natural
 color: Color
 parent: Node
 children: seq[Node]
  1. ---------------------------------------------------------------------------------------------------

proc leadingSpaces(line: string): int =

 ## return the number of leading spaces.
 while line[result] == ' ':
   inc result
  1. ---------------------------------------------------------------------------------------------------

proc buildTree(outline: string): tuple[root: Node, depth: Natural] =

 ## Build the tree for the given outline.
 result.root = Node()
 var level: int
 var startPos = @[-1]
 var nodes: seq[Node] = @[result.root]
 var linecount = 0
 for line in Outline.splitLines:
   inc linecount
   if line.len == 0: continue
   let start = line.leadingSpaces()
   level = startPos.find(start)
   if level < 0:
     # Level not yet encountered.
     if start < startPos[^1]:
       raise newException(ValueError, "wrong indentation at line " & $linecount)
     startPos.add(start)
     nodes.add(nil)
     level = startPos.high
   # Create the node.
   let node = Node(value: line.strip(), level: level)
   let parent = nodes[level - 1]
   parent.children.add(node)
   node.parent = parent
   nodes[level] = node   # Set the node as current node for this level.
 result.depth = nodes.high
  1. ---------------------------------------------------------------------------------------------------

proc padTree(node: Node; depth: Natural) =

 ## pad the tree with empty nodes to get an even depth.
 if node.level == depth:
   return
 if node.children.len == 0:
   # Add an empty node.
   node.children.add(Node(level: node.level + 1, parent: node))
 for child in node.children:
   child.padTree(depth)
  1. ---------------------------------------------------------------------------------------------------

proc computeWidths(node: Node) =

 ## Compute the widths.
 var width = 0
 if node.children.len == 0:
   width = 1
 else:
   for child in node.children:
     child.computeWidths()
     inc width, child.width
 node.width = width
  1. ---------------------------------------------------------------------------------------------------

proc build(nodelists: var seq[seq[Node]]; node: Node) =

 ## Build the list of nodes per level.
 nodelists[node.level].add(node)
 for child in node.children:
   nodelists.build(child)
  1. ---------------------------------------------------------------------------------------------------

proc setColors(nodelists: seq[seq[Node]]) =

 ## Set the colors of the nodes.
 for node in nodelists[1]:
   node.color = Line1Color
 for i, node in nodelists[2]:
   node.color = Line2Colors[i mod Line2Colors.len]
 for level in 3..nodelists.high:
   for node in nodelists[level]:
     node.color = if node.value.len != 0: node.parent.color else: NoColor
  1. ---------------------------------------------------------------------------------------------------

proc writeWikiTable(nodelists: seq[seq[Node]]) =

 ## Output the wikitable.
 echo "{| class='wikitable' style='text-align: center;'"
 for level in 1..nodelists.high:
   echo "|-"
   for node in nodelists[level]:
     if node.width > 1:
       # Node with children.
       echo "| style='background: $1 ' colspan=$2 | $3".format(node.color, node.width, node.value)
     elif node.value.len > 0:
       # Leaf with contents.
       echo "| style='background: $1 ' | $2".format(node.color, node.value)
     else:
       # Empty cell.
       echo "|  | "
 echo "|}"
  1. ---------------------------------------------------------------------------------------------------

proc writeHtml(nodelists: seq[seq[Node]]) =

 ## Output the HTML.

echo "

" for level in 1..nodelists.high: echo " " for node in nodelists[level]: if node.width > 1: # Node with children. echo " ".format(node.width, node.color, node.value)
     elif node.value.len > 0:
       # Leaf with contents.
echo " ".format(node.color, node.value)
     else:
       # Empty cell.
echo " " echo " " echo "
$3$2

"

  1. ———————————————————————————————————————————————————————————————————————————————————————————————————

let (root, depth) = Outline.buildTree() root.padTree(depth) root.computeWidths() var nodelists = newSeq[seq[Node]](depth + 1) nodelists.build(root) nodelists.setColors() nodelists.writeWikiTable() nodelists.writeHtml()</lang>

Output:

WikiTable:

Display an outline as a nested table.
Parse the outline to a tree, count the leaves descending from each node, and write out a table with 'colspan' values
measuring the indent of each line, translating the indentation to a nested structure, and padding the tree to even depth. defining the width of a leaf as 1, and the width of a parent node as a sum. either as a wiki table, or as HTML.
(The sum of the widths of its children)

HTML:

Display an outline as a nested table.
Parse the outline to a tree, count the leaves descending from each node, and write out a table with 'colspan' values
measuring the indent of each line, translating the indentation to a nested structure, and padding the tree to even depth. defining the width of a leaf as 1, and the width of a parent node as a sum. either as a wiki table, or as HTML.
(The sum of the widths of its children)

Perl

<lang perl>#!/usr/bin/perl

use strict; use warnings;

my @rows; my $row = -1; my $width = 0; my $color = 0; our $bg = 'e0ffe0';

parseoutline( do { local $/; =~ s/\t/ /gr } );

print "

\n"; for ( @rows ) { my $start = 0; print " \n"; for ( @$_ ) # columns { my ($data, $col, $span, $bg) = @$_; print " \n" x ( $col - $start ), " \n";
   $start = $col + $span;
   }
print " \n" x ( $width - $start ), " \n";
 }
print "
$data

\n";

sub parseoutline

 {
 ++$row;
 while( $_[0] =~ /^( *)(.*)\n((?:\1 .*\n)*)/gm )
   {
   my ($head, $body, $col) = ($2, $3, $width);
   $row == 1 and local $bg = qw( ffffe0 ffe0e0 )[ $color ^= 1];
   if( length $body ) { parseoutline( $body ) } else { ++$width }
   push @{ $rows[$row] }, [ $head, $col, $width - $col, $bg ];
   }
 --$row;
 }

__DATA__ Display an outline as a nested table.

   Parse the outline to a tree,
       measuring the indent of each line,
       translating the indentation to a nested structure,
       and padding the tree to even depth.
   count the leaves descending from each node,
       defining the width of a leaf as 1,
       and the width of a parent node as a sum.
           (The sum of the widths of its children)
   and write out a table with 'colspan' values
       either as a wiki table,
       or as HTML.</lang>
Output:
Display an outline as a nested table.
Parse the outline to a tree, count the leaves descending from each node, and write out a table with 'colspan' values
measuring the indent of each line, translating the indentation to a nested structure, and padding the tree to even depth. defining the width of a leaf as 1, and the width of a parent node as a sum. either as a wiki table, or as HTML.
(The sum of the widths of its children)

Phix

Can output in either html or wikitable markup <lang Phix>constant html = false,

        outlines = {"""

Display an outline as a nested table.

   Parse the outline to a tree,
       measuring the indent of each line,
       translating the indentation to a nested structure,
       and padding the tree to even depth.
   count the leaves descending from each node,
       defining the width of a leaf as 1,
       and the width of a parent node as a sum.
           (The sum of the widths of its children)
   and write out a table with 'colspan' values
       either as a wiki table,
       or as HTML.""",			"""

Display an outline as a nested table.

   Parse the outline to a tree,
       measuring the indent of each line,
       translating the indentation to a nested structure,
       and padding the tree to even depth. 
   count the leaves descending from each node,
       defining the width of a leaf as 1,
       and the width of a parent node as a sum.
           (The sum of the widths of its children)
           Propagating the sums upward as necessary. 
   and write out a table with 'colspan' values
       either as a wiki table,
       or as HTML.
   Optionally add color to the nodes."""}

constant yellow = "#ffffe6;",

        orange = "#ffebd2;",
        green  = "#f0fff0;",
        blue   = "#e6ffff;",
        pink   = "#ffeeff;",
        colours = {{yellow, orange, green, blue, pink},
                   {blue, yellow, orange, green, pink}}

function calc_spans(sequence lines, integer ldx)

   sequence children = lines[ldx][$]
   if length(children)!=0 then
       integer span = 0
       for i=1 to length(children) do
           integer child = children[i]
           lines = calc_spans(lines,child)
           span += lines[child][4]
       end for
       lines[ldx][4] = span

-- else -- (span already 1)

   end if
   return lines

end function

procedure markup(string outline, sequence colours)

   sequence lines = split(outline,"\n",no_empty:=true),
            pi = {},   -- indents (to locate parents)
            pdx = {},  -- indexes for ""
            children = {}
   string text
   integer maxdepth = 0,
           parent, depth, span
   for i=1 to length(lines) do
       string line = trim_tail(lines[i])
       text = trim_head(line)
       integer indent = length(line)-length(text)
       -- remove any completed parents
       while length(pi) and indent<=pi[$] do
           pi = pi[1..$-1]
           pdx = pdx[1..$-1]
       end while
       parent = 0
       if length(pi) then
           parent = pdx[$]
           lines[parent][$] &= i -- (update children)
       end if
       pi &= indent
       pdx &= i
       depth = length(pi)
       span = 1 -- (default/assume no children[=={}])
       lines[i] = {i,depth,indent,span,parent,text,children}
       maxdepth = max(maxdepth,depth)
   end for
   lines = calc_spans(lines,1)

string res = iff(html?"

\n"  :"{| class=\"wikitable\" style=\"text-align: center;\"\n") for d=1 to maxdepth do res &= iff(html?"\n"  :"|-\n") integer cdx = 1 for i=1 to length(lines) do {{},depth,{},span,parent,text,children} = lines[i] if depth=2 then cdx += 1 end if string style = sprintf(`style="background: %s"`,{colours[cdx]}) if depth=d then if span!=1 then style &= sprintf(` colspan="%d"`,span) end if res &= sprintf(iff(html?"\n"
                                      :"| %s | %s\n"),{style,text})
           elsif depth<d and children={} then
-- res &= iff(html?"\n"

--  :"| |\n")

res &= sprintf(iff(html?"\n"
                                      :"| %s |\n"),{style})
           end if
       end for
       if html then
res &= "\n" end if end for res &= iff(html?"
%s

\n"

                  :"|}\n")
   puts(1,res)

end procedure for i=1 to length(outlines) do

   markup(outlines[i],colours[i])

end for</lang>

Output:

in html:

Display an outline as a nested table.
Parse the outline to a tree, count the leaves descending from each node, and write out a table with 'colspan' values
measuring the indent of each line, translating the indentation to a nested structure, and padding the tree to even depth. defining the width of a leaf as 1, and the width of a parent node as a sum. either as a wiki table, or as HTML.
(The sum of the widths of its children)

and

Display an outline as a nested table.
Parse the outline to a tree, count the leaves descending from each node, and write out a table with 'colspan' values Optionally add color to the nodes.
measuring the indent of each line, translating the indentation to a nested structure, and padding the tree to even depth. defining the width of a leaf as 1, and the width of a parent node as a sum. either as a wiki table, or as HTML.
(The sum of the widths of its children) Propagating the sums upward as necessary.

or in wikitable markup:

Display an outline as a nested table.
Parse the outline to a tree, count the leaves descending from each node, and write out a table with 'colspan' values
measuring the indent of each line, translating the indentation to a nested structure, and padding the tree to even depth. defining the width of a leaf as 1, and the width of a parent node as a sum. either as a wiki table, or as HTML.
(The sum of the widths of its children)

and

Display an outline as a nested table.
Parse the outline to a tree, count the leaves descending from each node, and write out a table with 'colspan' values Optionally add color to the nodes.
measuring the indent of each line, translating the indentation to a nested structure, and padding the tree to even depth. defining the width of a leaf as 1, and the width of a parent node as a sum. either as a wiki table, or as HTML.
(The sum of the widths of its children) Propagating the sums upward as necessary.

Python

<lang python> """Display an outline as a nested table. Requires Python >=3.6."""

import itertools import re import sys

from collections import deque from typing import NamedTuple


RE_OUTLINE = re.compile(r"^((?: |\t)*)(.+)$", re.M)

COLORS = itertools.cycle(

   [
       "#ffffe6",
       "#ffebd2",
       "#f0fff0",
       "#e6ffff",
       "#ffeeff",
   ]

)


class Node:

   def __init__(self, indent, value, parent, children=None):
       self.indent = indent
       self.value = value
       self.parent = parent
       self.children = children or []
       self.color = None
   def depth(self):
       if self.parent:
           return self.parent.depth() + 1
       return -1
   def height(self):
       """Height of the subtree rooted at this node."""
       if not self.children:
           return 0
       return max(child.height() for child in self.children) + 1
   def colspan(self):
       if self.leaf:
           return 1
       return sum(child.colspan() for child in self.children)
   @property
   def leaf(self):
       return not bool(self.children)
   def __iter__(self):
       # Level order tree traversal.
       q = deque()
       q.append(self)
       while q:
           node = q.popleft()
           yield node
           q.extend(node.children)


class Token(NamedTuple):

   indent: int
   value: str


def tokenize(outline):

   """Generate ``Token``s from the given outline."""
   for match in RE_OUTLINE.finditer(outline):
       indent, value = match.groups()
       yield Token(len(indent), value)


def parse(outline):

   """Return the given outline as a tree of ``Node``s."""
   # Split the outline into lines and count the level of indentation.
   tokens = list(tokenize(outline))
   # Parse the tokens into a tree of nodes.
   temp_root = Node(-1, "", None)
   _parse(tokens, 0, temp_root)
   # Pad the tree so that all branches have the same depth.
   root = temp_root.children[0]
   pad_tree(root, root.height())
   return root


def _parse(tokens, index, node):

   """Recursively build a tree of nodes.
   Args:
       tokens (list): A collection of ``Token``s.
       index (int): Index of the current token.
       node (Node): Potential parent or sibling node.
   """
   # Base case. No more lines.
   if index >= len(tokens):
       return
   token = tokens[index]
   if token.indent == node.indent:
       # A sibling of node
       current = Node(token.indent, token.value, node.parent)
       node.parent.children.append(current)
       _parse(tokens, index + 1, current)
   elif token.indent > node.indent:
       # A child of node
       current = Node(token.indent, token.value, node)
       node.children.append(current)
       _parse(tokens, index + 1, current)
   elif token.indent < node.indent:
       # Try the node's parent until we find a sibling.
       _parse(tokens, index, node.parent)


def pad_tree(node, height):

   """Pad the tree with blank nodes so all branches have the same depth."""
   if node.leaf and node.depth() < height:
       pad_node = Node(node.indent + 1, "", node)
       node.children.append(pad_node)
   for child in node.children:
       pad_tree(child, height)


def color_tree(node):

   """Walk the tree and color each node as we go."""
   if not node.value:
       node.color = "#F9F9F9"
   elif node.depth() <= 1:
       node.color = next(COLORS)
   else:
       node.color = node.parent.color
   for child in node.children:
       color_tree(child)


def table_data(node):

   """Return an HTML table data element for the given node."""
   indent = "    "
   if node.colspan() > 1:
       colspan = f'colspan="{node.colspan()}"'
   else:
       colspan = ""
   if node.color:
       style = f'style="background-color: {node.color};"'
   else:
       style = ""
   attrs = " ".join([colspan, style])

return f"{indent}<td{attrs}>{node.value}" def html_table(tree): """Return the tree as an HTML table.""" # Number of columns in the table. table_cols = tree.colspan() # Running count of columns in the current row. row_cols = 0 # HTML buffer buf = ["

"] # Breadth first iteration. for node in tree: if row_cols == 0: buf.append(" ") buf.append(table_data(node)) row_cols += node.colspan() if row_cols == table_cols: buf.append(" ") row_cols = 0 buf.append("

")

   return "\n".join(buf)


def wiki_table_data(node):

   """Return an wiki table data string for the given node."""
   if not node.value:
       return "|  |"
   if node.colspan() > 1:
       colspan = f"colspan={node.colspan()}"
   else:
       colspan = ""
   if node.color:
       style = f'style="background: {node.color};"'
   else:
       style = ""
   attrs = " ".join([colspan, style])
   return f"| {attrs} | {node.value}"


def wiki_table(tree):

   """Return the tree as a wiki table."""
   # Number of columns in the table.
   table_cols = tree.colspan()
   # Running count of columns in the current row.
   row_cols = 0
   # HTML buffer
   buf = ['{| class="wikitable" style="text-align: center;"']
   for node in tree:
       if row_cols == 0:
           buf.append("|-")
       buf.append(wiki_table_data(node))
       row_cols += node.colspan()
       if row_cols == table_cols:
           row_cols = 0
   buf.append("|}")
   return "\n".join(buf)


def example(table_format="wiki"):

   """Write an example table to stdout in either HTML or Wiki format."""
   outline = (
       "Display an outline as a nested table.\n"
       "    Parse the outline to a tree,\n"
       "        measuring the indent of each line,\n"
       "        translating the indentation to a nested structure,\n"
       "        and padding the tree to even depth.\n"
       "    count the leaves descending from each node,\n"
       "        defining the width of a leaf as 1,\n"
       "        and the width of a parent node as a sum.\n"
       "            (The sum of the widths of its children)\n"
       "    and write out a table with 'colspan' values\n"
       "        either as a wiki table,\n"
       "        or as HTML."
   )
   tree = parse(outline)
   color_tree(tree)
   if table_format == "wiki":
       print(wiki_table(tree))
   else:
       print(html_table(tree))


if __name__ == "__main__":

   args = sys.argv[1:]
   if len(args) == 1:
       table_format = args[0]
   else:
       table_format = "wiki"
   example(table_format)

</lang>

Output:

Wiki table

Display an outline as a nested table.
Parse the outline to a tree, count the leaves descending from each node, and write out a table with 'colspan' values
measuring the indent of each line, translating the indentation to a nested structure, and padding the tree to even depth. defining the width of a leaf as 1, and the width of a parent node as a sum. either as a wiki table, or as HTML.
(The sum of the widths of its children)

HTML table

<table style='text-align: center;'>
  <tr>
    <tdcolspan="7" style="background-color: #ffffe6;">Display an outline as a nested table.</td>
  </tr>
  <tr>
    <tdcolspan="3" style="background-color: #ffebd2;">Parse the outline to a tree,</td>
    <tdcolspan="2" style="background-color: #f0fff0;">count the leaves descending from each node,</td>
    <tdcolspan="2" style="background-color: #e6ffff;">and write out a table with 'colspan' values</td>
  </tr>
  <tr>
    <td style="background-color: #ffebd2;">measuring the indent of each line,</td>
    <td style="background-color: #ffebd2;">translating the indentation to a nested structure,</td>
    <td style="background-color: #ffebd2;">and padding the tree to even depth.</td>
    <td style="background-color: #f0fff0;">defining the width of a leaf as 1,</td>
    <td style="background-color: #f0fff0;">and the width of a parent node as a sum.</td>
    <td style="background-color: #e6ffff;">either as a wiki table,</td>
    <td style="background-color: #e6ffff;">or as HTML.</td>
  </tr>
  <tr>
    <td style="background-color: #F9F9F9;"></td>
    <td style="background-color: #F9F9F9;"></td>
    <td style="background-color: #F9F9F9;"></td>
    <td style="background-color: #F9F9F9;"></td>
    <td style="background-color: #f0fff0;">(The sum of the widths of its children)</td>
    <td style="background-color: #F9F9F9;"></td>
    <td style="background-color: #F9F9F9;"></td>
  </tr>
</table>

Raku

(formerly Perl 6)

Works with: Rakudo version 2019.07.1

Use a slightly more complicated outline than the task example to test some edge conditions. Limited to 10 direct subnodes on any one node as is. Easily adapted for larger if necessary.

Strictly speaking, this is not a nested table. It is just a single level table that has some column spans > 1. For an example of using actual nested tables, see the task entry: List_authors_of_task_descriptions#Raku, (and full output).

<lang perl6>my $outline = q:to/END/;

   Display an outline as a nested table.
       Parse the outline to a tree,
           measuring the indent of each line,
           translating the indentation to a nested structure,
           and padding the tree to even depth.
       count the leaves descending from each node,
           defining the width of a leaf as 1,
           and the width of a parent node as a sum.
               (The sum of the widths of its children)
               Propagating the sums upward as necessary.
       and write out a table with 'colspan' values
           either as a wiki table,
           or as HTML.
       Optionally add color to the nodes.
   END
  1. Import outline paragraph into native data structure

sub import (Str $trees, $level = ' ') {

   my $forest;
   my $last = -Inf;
   for $trees.lines -> $branch {
       $branch ~~ / ($($level))* /;
       my $this = +$0;
       $forest ~= do {
           given $this cmp $last {
               when More { "\['{esc $branch.trim}', " }
               when Same { "'{esc $branch.trim}', " }
               when Less { "{']' x $last - $this}, '{esc $branch.trim}', " }
           }
       }
       $last = $this;
   }
   sub esc { $^s.subst( /(<['\\]>)/, -> $/ { "\\$0" }, :g) }
   $forest ~= ']' x 1 + $last;
   use MONKEY-SEE-NO-EVAL;
   $forest.EVAL;

}

my @AoA = import $outline, ' '; my @layout;

  1. Collect information about node depth, position and children

{

   my @width = 0;
   my $depth = -1;
   @AoA.&insert;
   multi insert ($item) {
       @width[*-1]++;
       @layout.push: { :depth($depth.clone), :id(@width[*-1].clone), :text($item) };
   }
   multi insert (@array) {
       @width.push: @width[*-1] * 10;
       ++$depth;
       @array.map: &insert;
       --$depth;
       @width.pop;
   }

}

my $max-depth = @layout.max( *.<depth> )<depth>;

  1. Pad ragged nodes

for (^$max-depth) -> $d {

   my @nodes = @layout.grep( *.<depth> == $d );
   for @nodes.sort( +*.<id> ) -> $n {
       unless @layout.first( *.<id> == $n<id> ~ 1 ) {
           @layout.push: { :depth($n<depth> + 1), :id($n<id> *10 + 1), :text() };
       }
   }

}

  1. Calculate spans (child nodes)

for (0..$max-depth).reverse -> $d {

   my @nodes = @layout.grep( *.<depth> == $d );
   for @nodes.sort( +*.<id> ) -> $n {
       my @span = @layout.grep: {.<depth> == $d + 1 && .<id>.starts-with: $n<id> };
       $n = ( sum @span.map( { . // 0} )) || +@span || 1;
   }

}

  1. Programatically assign colors

for (0..$max-depth) -> $d {

   my @nodes = @layout.grep( *.<depth> == $d );
   my $incr = 1 / (1 + @nodes);
   for @nodes.sort( +*.<id> ) -> $n {
       my $color = $d > 1 ??
       @layout.first( *.<id> eq $n<id>.chop )<color> !!
       "style=\"background: #" ~ hsv2rgb( ++$ * $incr, .1, 1) ~ '" ';
       $n<color> = $n<text> ?? $color !! ;
   }

}

  1. Generate wikitable

say '{| class="wikitable" style="text-align: center;"' ~ "\n" ~ (join "\n|-\n", (0..$max-depth).map: -> $d {

   my @nodes = @layout.grep( *.<depth> == $d );
   (join "\n", @nodes.sort( +*.<id> ).map( -> $node {
       '| ' ~
       ($node<color> //  ) ~
       ($node > 1 ?? "colspan=$node" !!  ) ~
       ' | ' ~ $node<text> }
   ))

}) ~ "\n|}";

say "\n\nSometimes it makes more sense to display an outline as... well... as an outline, rather than as a table." ~ Q|¯\_(ツ)_/¯| ~ "\n";

{ ## Outline - Ordered List #######

   my @type = <upper-roman upper-latin decimal lower-latin lower-roman>;
   my $depth = 0;

multi ol ($item) { "\

  • $item\n" } multi ol (@array) { my $li = $depth ?? "
  • " !! ; $depth++; my $list = "

      \n" ~ ( @array.map( &ol ).join ) ~ "

    $li\n";

           $depth--;
           $list
       }
    

    say "

    \n" ~ @AoA.&ol ~ "

    ";

    }

    sub hsv2rgb ( $h, $s, $v ){

       my $c = $v * $s;
       my $x = $c * (1 - abs( (($h*6) % 2) - 1 ) );
       my $m = $v - $c;
       my ($r, $g, $b) = do given $h {
           when   0..^(1/6) { $c, $x, 0 }
           when 1/6..^(1/3) { $x, $c, 0 }
           when 1/3..^(1/2) { 0, $c, $x }
           when 1/2..^(2/3) { 0, $x, $c }
           when 2/3..^(5/6) { $x, 0, $c }
           when 5/6..1      { $c, 0, $x }
       }
       ( $r, $g, $b ).map( ((*+$m) * 255).Int)».base(16).join
    

    }</lang>

    Output:
    Display an outline as a nested table.
    Parse the outline to a tree, count the leaves descending from each node, and write out a table with 'colspan' values Optionally add color to the nodes.
    measuring the indent of each line, translating the indentation to a nested structure, and padding the tree to even depth. defining the width of a leaf as 1, and the width of a parent node as a sum. either as a wiki table, or as HTML.
    (The sum of the widths of its children) Propagating the sums upward as necessary.


    Sometimes it makes more sense to display an outline as... well... as an outline, rather than as a table.¯\_(ツ)_/¯

    1. Display an outline as a nested table.
      1. Parse the outline to a tree,
        1. measuring the indent of each line,
        2. translating the indentation to a nested structure,
        3. and padding the tree to even depth.
      2. count the leaves descending from each node,
        1. defining the width of a leaf as 1,
        2. and the width of a parent node as a sum.
          1. (The sum of the widths of its children)
          2. Propagating the sums upward as necessary.
      3. and write out a table with 'colspan' values
        1. either as a wiki table,
        2. or as HTML.
      4. Optionally add color to the nodes.

    zkl

    <lang zkl>fcn parseOutline(outline){ //--> "tree" annotated with spans

      var [const] indent=" "*100;		// no tabs
    
      parse:=fcn(ow,tree,parent,col,prevD,unit){
         rows,span,spans,cell := 0, 0,List(), Void;
         foreach line in (ow){
    

    if(not line) continue; d,text,d := line.prefix(indent), line[d,*], d/unit; // d==0 is boo-boo

            if(d==prevD){		// assume a leaf
    

    rows=rows.max(d); // zero based col+=1; span+=1; cell=List(d,col,1,text); // cell: (depth, col offset, span, text) tree.append(cell); } else if(d>prevD){ // down a level ow.push(line); r,s := self.fcn(ow,tree,cell,col-1,d,unit); rows = rows.max(r); spans.append(s); } else{ // d<prevD: done with this branch, back out to level above ow.push(line); break; }

         }
         span=( spans and (spans.sum(0) + span - 1) or span ).max(1);
         parent[2]=span;
         return(rows,span);
      };
    
      ow,title,trees := outline.walker(11), ow.next(), List();
      line,unit := ow.peek(), line.prefix(indent);	// no leading space == bad
      rows,cols := 0,0;
      foreach line in (ow){	// the major (same color) columns
         tree:=List(0, cell:=List(1, 1,1, line.strip()) );
         trees.append(tree);
         r,c := parse(ow,tree,cell,0,2,unit);
         tree[0]=c;	// span for this "branch"
         rows,cols = rows.max(r), cols + c;
      }
      return(rows+1,cols,title,trees);
    

    }

    fcn makeMarkup(rows,cols,title,trees){

      var [const] colors=L("#ffebd2","#f0fff0","#e6ffff","#ffeeff");
      out,cell := Data(Void), 0'~| style="background: %s " colspan=%d | %s~.fmt;
      out.writeln(0'~{| class="wikitable" style="text-align: center;"~,"\n|-\n",
        cell("#ffffe6;",cols,title));
      foreach row in ([1..rows-1]){
         clrs:=Walker.cycle(colors);
         out.writeln("|-");
         foreach t in (trees){	// create this row
    

    span,clr := t[0], clrs.next(); col,cols := 1, t[1,*].filter('wrap([(d,_,text)]){ d==row }); foreach _,cpos,cspan,text in (cols){ if(col<cpos){ out.writeln(cell(clr,cpos-col,"")); col=cpos } out.writeln(cell(clr,cspan,text)); col+=cspan; } // col is span+1 after loop if all cells had text if(col<=span) out.writeln(cell(clr,span-col+1,""));

         }
      }
      out.writeln("|}");
      out.text
    

    }</lang> <lang zkl>outlineText:=Data(Void,

    1. <<<

    "Display an outline as a nested table.

       Parse the outline to a tree,
           measuring the indent of each line,
           translating the indentation to a nested structure,
           and padding the tree to even depth.
       count the leaves descending from each node,
           defining the width of a leaf as 1,
           and the width of a parent node as a sum.
               (The sum of the widths of its children)
       and write out a table with 'colspan' values
           either as a wiki table,
           or as HTML.
    

    ");

    1. <<<

    rows,cols,title,trees := parseOutline(outlineText); makeMarkup(rows,cols,title,trees).println();</lang>

    Output:
    Display an outline as a nested table.
    Parse the outline to a tree, count the leaves descending from each node, and write out a table with 'colspan' values
    measuring the indent of each line, translating the indentation to a nested structure, and padding the tree to even depth. defining the width of a leaf as 1, and the width of a parent node as a sum. either as a wiki table, or as HTML.
    (The sum of the widths of its children)


    And the Raku example: <lang zkl>outlineText:=Data(Void,

    1. <<<

    "Display an outline as a nested table.

       Parse the outline to a tree,
           measuring the indent of each line,
           translating the indentation to a nested structure,
           and padding the tree to even depth. 
       count the leaves descending from each node,
           defining the width of a leaf as 1,
           and the width of a parent node as a sum.
               (The sum of the widths of its children)
               Propagating the sums upward as necessary. 
       and write out a table with 'colspan' values
           either as a wiki table,
           or as HTML.
       Optionally add color to the nodes.
    

    ");

    1. <<<

    rows,cols,title,trees := parseOutline(outlineText); makeMarkup(rows,cols,title,trees).println();</lang>

    Output:
    Display an outline as a nested table.
    Parse the outline to a tree, count the leaves descending from each node, and write out a table with 'colspan' values Optionally add color to the nodes.
    measuring the indent of each line, translating the indentation to a nested structure, and padding the tree to even depth. defining the width of a leaf as 1, and the width of a parent node as a sum. either as a wiki table, or as HTML.
    (The sum of the widths of its children) Propagating the sums upward as necessary.