Display an outline as a nested table

From Rosetta Code
Display an outline as a nested table is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.
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[edit]

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))
}
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[edit]

(() => {
'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, trimmed Text) tuples.
const
indentTextPairs = xs.map(compose(
firstArrow(length), span(isSpace)
)),
indentUnit = minimum(indentTextPairs.flatMap(pair => {
const w = fst(pair);
return 0 < w ? [w] : [];
}));
return indentTextPairs.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 ----------------------------
 
// Node :: a -> [Tree a] -> Tree a
const Node = v => xs => ({
type: 'Node',
root: v, // any type of value (consistent across tree)
nest: xs || []
});
 
// 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);
 
// Lift a simple function to one which applies to a tuple,
// transforming only the first item of the tuple
 
// 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]/);
 
// 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);
};
 
// minimum :: Ord a => [a] -> a
const minimum = xs =>
0 < xs.length ? (
foldl1(a => x => x < a ? x : a)(xs)
) : undefined;
 
// 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();
})();
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[edit]

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("<h4>A Rosetta Code Nested Table</h4><table style=\"width:100%\" class=\"wikitable\" >")
for ind in minimum(df.INDENT):maximum(df.INDENT)
println("<tr>")
for row in eachrow(df)
if row[:INDENT] == ind
if row[:PRESPAN] > 0
println("<td colspan=\"$(row[:PRESPAN])\"> </td>")
end
print("<td ")
if row[:COLSPAN] > 0
println("colspan=\"$(row[:COLSPAN])\"")
end
println(" style = \"$(colorstring(row[:LEVELONEPARENT]))\" >$(row[:TEXT])</td>")
end
end
println("</tr>")
end
println("</table>")
end
 
htmlfromdataframe(processtable(text))
textplus = text * " Optionally add color to the nodes."
htmlfromdataframe(processtable(textplus))
 
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)

Perl 6[edit]

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#Perl_6, (and full output).

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
 
# 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;
 
# 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>;
 
# 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('') };
}
}
}
 
# 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<span> = ( sum @span.map( { .<span> // 0} )) || +@span || 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 !! '';
}
}
 
# 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<span> > 1 ?? "colspan=$node<span>" !! '' ) ~
' | ' ~ $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) { "\<li>$item\n" }
 
multi ol (@array) {
my $li = $depth ?? "</li>" !! '';
$depth++;
my $list = "<ol style=\"list-style: {@type[$depth - 1]};\">\n" ~
( @array.map( *.&ol ).join ) ~ "</ol>$li\n";
$depth--;
$list
}
 
say "<div style=\"background: #fee;\">\n" ~ @AoA.&ol ~ "</div>";
}
 
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
}
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.

Phix[edit]

Can output in either html or wikitable markup

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?"<table class=\"wikitable\" style=\"text-align: center;\">\n"
 :"{| class=\"wikitable\" style=\"text-align: center;\"\n")
for d=1 to maxdepth do
res &= iff(html?"<tr>\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?"<td %s>%s</td>\n"
 :"| %s | %s\n"),{style,text})
elsif depth<d and children={} then
-- res &= iff(html?"<td></td>\n"
--  :"| |\n")
res &= sprintf(iff(html?"<td %s></td>\n"
 :"| %s |\n"),{style})
end if
end for
if html then
res &= "</tr>\n"
end if
end for
res &= iff(html?"</table>\n"
 :"|}\n")
puts(1,res)
end procedure
for i=1 to length(outlines) do
markup(outlines[i],colours[i])
end for
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.

zkl[edit]

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
}
outlineText:=Data(Void,
#<<<
"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.
");
#<<<
 
rows,cols,title,trees := parseOutline(outlineText);
makeMarkup(rows,cols,title,trees).println();
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 Perl6 example:

outlineText:=Data(Void,
#<<<
"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.
");
#<<<
 
rows,cols,title,trees := parseOutline(outlineText);
makeMarkup(rows,cols,title,trees).println();
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.