Display an outline as a nested table: Difference between revisions

Content added Content deleted
(→‎{{header|JavaScript}}: Updated primitives, reshaped to handle forests (multiple roots) and foreground parallels with other versions)
Line 673: Line 673:
=={{header|JavaScript}}==
=={{header|JavaScript}}==
<lang javascript>(() => {
<lang javascript>(() => {
'use strict';
"use strict";


// ----------- NESTED TABLES FROM OUTLINE ------------

// wikiTablesFromOutline :: [String] -> String -> String
const wikiTablesFromOutline = colorSwatch =>
outline => forestFromIndentedLines(
indentLevelsFromLines(lines(outline))
)
.map(wikiTableFromTree(colorSwatch));


// wikiTableFromTree :: [String] -> Tree String -> String
const wikiTableFromTree = colorSwatch =>
compose(
wikiTableFromRows,
levels,
paintedTree(colorSwatch),
widthLabelledTree,
ap(paddedTree(""))(treeDepth)
);

// ---------------------- TEST -----------------------
// main :: IO ()
// main :: IO ()
const main = () => {
const main = () => {
Line 688: Line 709:
and write out a table with 'colspan' values
and write out a table with 'colspan' values
either as a wiki table,
either as a wiki table,
or as HTML.`
or as HTML.`;


console.log(
return wikiTablesFromOutline([
wikiTableFromForest(
"#ffffe6",
forestOfEvenDepth(
"#ffebd2",
map(compose(
"#f0fff0",
"#e6ffff",
coloredKeyLines(Object.keys(dictColors)),
paintedTree('backColor')('yellow'),
"#ffeeff"
measuredTree
])(outline);
))(
forestFromOutline(outline)
)
)
)
);
};
};


// TRANSLATION OF OUTLINE TO NESTED TABLE -----------------
// --------- TREE STRUCTURE FROM NESTED TEXT ---------


// forestFromOutline :: String -> [Tree String]
// forestFromIndentedLines :: [(Int, String)] ->
// [Tree String]
const forestFromOutline = s =>
const forestFromIndentedLines = tuples => {
// A list of trees, derived from an
// indented outline text.
const go = xs =>
forestFromLineIndents(
0 < xs.length ? (() => {
indentLevelsFromLines(lines(s))
// First line and its sub-tree,
const [indented, body] = Array.from(
);
xs[0]
),
[tree, rest] = Array.from(
span(compose(lt(indented), fst))(
tail(xs)
)
);


// followed by the rest.
// wikiTableFromForest :: [Tree (String, Dict)] -> String
return [
const wikiTableFromForest = forest => {
// Lines of wiki markup representing a nested tree
Node(body)(go(tree))
// with 'colspan' values for parent nodes,
].concat(go(rest));
// and varying background colors.
})() : [];

const tableRows = trees => {
return go(tuples);
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)]
// indentLevelsFromLines :: [String] -> [(Int, String)]
const indentLevelsFromLines = xs => {
const indentLevelsFromLines = xs => {
// A list of (indentLevel, Text) tuples.
const
const
pairs = xs.map(compose(
pairs = xs.map(
firstArrow(length), span(isSpace)
x => bimap(length)(cs => cs.join(""))(
)),
span(isSpace)(list(x))
indentUnit = maybe(1)(fst)(
)
find(x => 0 < x[0])(pairs)
),
)
indentUnit = pairs.reduce(
return pairs.map(
(a, tpl) => {
firstArrow(flip(div)(indentUnit))
const i = tpl[0];

);
return 0 < i ? (
i < a ? i : a
) : a;
},
Infinity
);

return [Infinity, 0].includes(indentUnit) ? (
pairs
) : pairs.map(first(n => n / indentUnit));
};
};


// ------------ TREE PADDED TO EVEN DEPTH ------------
// forestFromLineIndents :: [(Int, String)] -> [Tree String]

const forestFromLineIndents = tuples => {
// A list of nested trees derived from
// paddedTree :: a -> Tree a -> Int -> Tree a
const paddedTree = padValue =>
// a list of indented lines.
const go = xs =>
// All descendants expanded to same depth
0 < xs.length ? (() => {
// with empty nodes where needed.
const [n, s] = Array.from(xs[0]);
node => depth => {
// Lines indented under this line,
const go = n => tree =>
// tupled with all the rest.
1 < n ? (() => {
const [firstTreeLines, rest] = Array.from(
const children = nest(tree);

span(x => n < x[0])(xs.slice(1))
return Node(root(tree))(
(
0 < children.length ? (
children
) : [Node(padValue)([])]
).map(go(n - 1))
);
})() : tree;

return go(depth)(node);
};

// treeDepth :: Tree a -> Int
const treeDepth = tree =>
foldTree(
() => xs => 0 < xs.length ? (
1 + maximum(xs)
) : 1
)(tree);

// ------------- SUBTREE WIDTHS MEASURED -------------

// widthLabelledTree :: Tree a -> Tree (a, Int)
const widthLabelledTree = tree =>
// A tree in which each node is labelled with
// the width of its own subtree.
foldTree(x => xs =>
0 < xs.length ? (
Node(Tuple(x)(
xs.reduce(
(a, node) => a + snd(root(node)),
0
)
))(xs)
) : Node(Tuple(x)(1))([])
)(tree);

// -------------- COLOR SWATCH APPLIED ---------------

// paintedTree :: [String] -> Tree a -> Tree (String, a)
const paintedTree = colorSwatch =>
tree => 0 < colorSwatch.length ? (
Node(
Tuple(colorSwatch[0])(root(tree))
)(
zipWith(compose(fmapTree, Tuple))(
cycle(colorSwatch.slice(1))
)(
nest(tree)
)
)
) : fmapTree(Tuple(""))(tree);

// --------------- WIKITABLE RENDERED ----------------

// wikiTableFromRows ::
// [[(String, (String, Int))]] -> String
const wikiTableFromRows = rows => {
const
cw = color => width => {
const go = w =>
1 < w ? (
`colspan=${w} `
) : "";

return `style="background:${color}; "` + (
` ${go(width)}`
);
);
// This first tree, and then the rest.
},
return [Node(s)(go(firstTreeLines))]
cellText = ctw => {
.concat(go(rest));
const [color, tw] = Array.from(ctw);
})() : [];
const [txt, width] = Array.from(tw);

return go(tuples);
return 0 < txt.length ? (
`| ${cw(color)(width)}| ${txt}`
) : "| |";
},
classText = "class=\"wikitable\"",
styleText = "style=\"text-align:center;\"",
header = `{| ${classText} ${styleText}\n|-`,
tableBody = rows.map(
cells => cells.map(cellText).join("\n")
).join("\n|-\n");

return `${header}\n${tableBody}\n|}`;
};
};


// ------------------ GENERIC TREES ------------------
// forestOfEvenDepth :: [Tree (a, Dict)] -> [Tree (a, Dict)]

const forestOfEvenDepth = measuredTrees => {
// A tree padded downwards so that every branch
// Node :: a -> [Tree a] -> Tree a
const Node = v =>
// descends to the same depth.
const go = n => tree =>
// Constructor for a Tree node which connects a
1 >= n ? (
// value of some kind to a list of zero or
tree
// more child trees.
) : Node(tree.root)(
xs => ({
0 < tree.nest.length ? (
type: "Node",
tree.nest.map(go(n - 1))
root: v,
) : [Node(Tuple('')({}))([])]
nest: xs || []
);
});

return measuredTrees.map(go(

1 + maximumBy(x => root(x)[1].layerSum)(
measuredTrees
// fmapTree :: (a -> b) -> Tree a -> Tree b
const fmapTree = f => {
).root[1].layerSum
));
// A new tree. The result of a
// structure-preserving application of f
// to each root in the existing tree.
const go = t => Node(
f(t.root)
)(
t.nest.map(go)
);

return go;
};
};


// BACKGROUND COLOURS FOR SECTIONS OF THE TREE ----


// coloredKeyLines :: [String] ->
// foldTree :: (a -> [b] -> b) -> Tree a -> b
const foldTree = f => {
// Tree (String, Dict) -> Tree (String, Dict)
// The catamorphism on trees. A summary
const coloredKeyLines = colorNames => node =>
// value obtained by a depth-first fold.
Node(root(node))(
zipWith(paintedTree('backColor'))(
const go = tree => f(
take(node.nest.length)(
root(tree)
cycle(tail(colorNames))
)(
)
nest(tree).map(go)
)(node.nest)
);
);


return go;
// 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
// levels :: Tree a -> [[a]]
const dictColors = {
yellow: '#ffffe6',
const levels = tree => {
// A list of lists, grouping the root
orange: '#ffebd2',
// values of each level of the tree.
green: '#f0fff0',
blue: '#e6ffff',
const go = (a, node) => {
const [h, ...t] = 0 < a.length ? (
pink: '#ffeeff',
gray: ''
a
) : [
[],
[]
];

return [
[node.root, ...h],
...node.nest.slice(0)
.reverse()
.reduce(go, t)
];
};

return go([], tree);
};
};




// nest :: Tree a -> [a]
// GENERIC FUNCTIONS ----------------------------
const nest = tree => {
// Allowing for lazy (on-demand) evaluation.
// If the nest turns out to be a function –
// rather than a list – that function is applied
// here to the root, and returns a list.
const xs = tree.nest;

return "function" !== typeof xs ? (
xs
) : xs(root(tree));
};


// root :: Tree a -> a
const root = tree =>
// The value attached to a tree node.
tree.root;

// --------------------- GENERIC ---------------------


// Just :: a -> Maybe a
// Just :: a -> Maybe a
const Just = x => ({
const Just = x => ({
type: 'Maybe',
type: "Maybe",
Nothing: false,
Nothing: false,
Just: x
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
// Nothing :: Maybe a
const Nothing = () => ({
const Nothing = () => ({
type: 'Maybe',
type: "Maybe",
Nothing: true,
Nothing: true
});
});



// Tuple (,) :: a -> b -> (a, b)
// Tuple (,) :: a -> b -> (a, b)
const Tuple = a => b => ({
const Tuple = a =>
type: 'Tuple',
b => ({
'0': a,
type: "Tuple",
'1': b,
"0": a,
length: 2
"1": b,
length: 2
});
});


// apFn :: (a -> b -> c) -> (a -> b) -> (a -> c)
const ap = f =>
// Applicative instance for functions.
// f(x) applied to g(x).
g => x => f(x)(
g(x)
);


// bimap :: (a -> b) -> (c -> d) -> (a, c) -> (b, d)
const bimap = f =>
// Tuple instance of bimap.
// A tuple of the application of f and g to the
// first and second values respectively.
g => tpl => Tuple(f(tpl[0]))(
g(tpl[1])
);



// compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
// compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
const compose = (...fs) =>
const compose = (...fs) =>
x => fs.reduceRight((a, f) => f(a), x);
// A function defined by the right-to-left
// composition of all the functions in fs.
fs.reduce(
(f, g) => x => f(g(x)),
x => 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]
// cycle :: [a] -> Generator [a]
function* cycle(xs) {
const cycle = function* (xs) {
// An infinite repetition of xs,
// from which an arbitrary prefix
// may be taken.
const lng = xs.length;
const lng = xs.length;
let i = 0;
let i = 0;

while (true) {
while (true) {
yield(xs[i])
yield xs[i];
i = (1 + i) % lng;
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))
// first :: (a -> b) -> ((a, c) -> (b, c))
const firstArrow = f =>
const first = f =>
// A simple function lifted to one which applies
// A simple function lifted to one which applies
// to a tuple, transforming only its first item.
// to a tuple, transforming only its first item.
xy => Tuple(f(xy[0]))(
xy => {
xy[1]
const tpl = Tuple(f(xy[0]))(xy[1]);
);


// flip :: (a -> b -> c) -> b -> a -> c
return Array.isArray(xy) ? (
Array.from(tpl)
const flip = f =>
1 < f.length ? (
) : tpl;
(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
// fst :: (a, b) -> a
const fst = tpl => tpl[0];
const fst = tpl =>
// First member of a pair.
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
// isSpace :: Char -> Bool
const isSpace = c => /\s/.test(c);
const isSpace = c =>
// True if c is a white space character.
(/\s/u).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
// length :: [a] -> Int
const length = xs =>
const length = xs =>
// Returns Infinity over objects without finite
(Array.isArray(xs) || 'string' === typeof xs) ? (
// length. This enables zip and zipWith to choose
// the shorter argument when one is non-finite,
// like cycle, repeat etc
"GeneratorFunction" !== xs.constructor
.constructor.name ? (
xs.length
xs.length
) : Infinity;
) : 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]
// lines :: String -> [String]
const lines = s => s.split(/[\r\n]/);
const lines = s =>
// A list of strings derived from a single
// string delimited by newline and or CR.
0 < s.length ? (
s.split(/[\r\n]+/u)
) : [];


// 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]
// list :: StringOrArrayLike b => b -> [a]
const map = f => xs =>
const list = xs =>
(Array.isArray(xs) ? (
// xs itself, if it is an Array,
// or an Array derived from xs.
Array.isArray(xs) ? (
xs
xs
) : xs.split('')).map(f);
) : Array.from(xs || []);


// max :: Ord a => a -> a -> a
const max = a => b => gt(b)(a) ? b : a;


// maximumBy :: (a -> a -> Ordering) -> [a] -> a
// lt (<) :: Ord a => a -> a -> Bool
const maximumBy = f => xs =>
const lt = a =>
0 < xs.length ? (
b => a < b;
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]
// maximum :: Ord a => [a] -> a
const nest = tree => tree.nest;
const maximum = xs => (
// The largest value in a non-empty list.
ys => 0 < ys.length ? (
ys.slice(1).reduce(
(a, y) => y > a ? (
y
) : a, ys[0]
)
) : undefined
)(list(xs));


// root :: Tree a -> a
const root = tree => tree.root;


// snd :: (a, b) -> b
// snd :: (a, b) -> b
const snd = tpl => tpl[1];
const snd = tpl =>
// Second member of a pair.
tpl[1];



// span :: (a -> Bool) -> [a] -> ([a], [a])
// span :: (a -> Bool) -> [a] -> ([a], [a])
const span = p => xs => {
const span = p =>
const iLast = xs.length - 1;
// Longest prefix of xs consisting of elements which
// all satisfy p, tupled with the remainder of xs.
return splitAt(
until(i => iLast < i || !p(xs[i]))(
xs => {
succ
const i = xs.findIndex(x => !p(x));
)(0)
)(xs);
};


// splitAt :: Int -> [a] -> ([a], [a])
return -1 !== i ? (
const splitAt = n => xs =>
Tuple(xs.slice(0, i))(
Tuple(xs.slice(0, n))(
xs.slice(i)
xs.slice(n)
)
);
) : Tuple(xs)([]);
};

// str :: a -> String
const str = x => x.toString();


// succ :: Enum a => a -> a
const succ = x => 1 + x;


// tail :: [a] -> [a]
// tail :: [a] -> [a]
const tail = xs => 0 < xs.length ? xs.slice(1) : [];
const tail = xs =>
// A new list consisting of all
// items of xs except the first.
"GeneratorFunction" !== xs.constructor
.constructor.name ? (
(ys => 0 < ys.length ? ys.slice(1) : [])(
xs
)
) : (take(1)(xs), xs);



// take :: Int -> [a] -> [a]
// take :: Int -> [a] -> [a]
// take :: Int -> String -> String
// take :: Int -> String -> String
const take = n => xs =>
const take = n =>
// The first n elements of a list,
'GeneratorFunction' !== xs.constructor.constructor.name ? (
// string of characters, or stream.
xs => "GeneratorFunction" !== xs
.constructor.constructor.name ? (
xs.slice(0, n)
xs.slice(0, n)
) : [].concat.apply([], Array.from({
) : [].concat(...Array.from({
length: n
length: n
}, () => {
}, () => {
const x = xs.next();
const x = xs.next();

return x.done ? [] : [x.value];
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
// uncons :: [a] -> Maybe (a, [a])
const unlines = xs => xs.join('\n');
const uncons = xs => {
// Just a tuple of the head of xs and its tail,
// Or Nothing if xs is an empty list.
const lng = length(xs);


// until :: (a -> Bool) -> (a -> a) -> a -> a
return (0 < lng) ? (
const until = p => f => x => {
Infinity > lng ? (
let v = x;
// Finite list
while (!p(v)) v = f(v);
Just(Tuple(xs[0])(xs.slice(1)))
return v;
) : (() => {
// Lazy generator
const nxt = take(1)(xs);

return 0 < nxt.length ? (
Just(Tuple(nxt[0])(xs))
) : Nothing();
})()
) : Nothing();
};
};



// zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
// zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
const zipWith = f => xs => ys =>
const zipWith = f =>
// A list with the length of the shorter of
xs.slice(
0, Math.min(xs.length, ys.length)
// xs and ys, defined by zipping with a
).map((x, i) => f(x)(ys[i]));
// custom function, rather than with the
// default tuple constructor.
xs => ys => {
const n = Math.min(length(xs), length(ys));

return Infinity > n ? (
(([as, bs]) => Array.from({
length: n
}, (_, i) => f(as[i])(
bs[i]
)))([xs, ys].map(
take(n)
))
) : zipWithGen(f)(xs)(ys);
};


// zipWithGen :: (a -> b -> c) ->
// Gen [a] -> Gen [b] -> Gen [c]
const zipWithGen = f => ga => gb => {
const go = function* (ma, mb) {
let
a = ma,
b = mb;

while (!a.Nothing && !b.Nothing) {
const
ta = a.Just,
tb = b.Just;

yield f(fst(ta))(fst(tb));
a = uncons(snd(ta));
b = uncons(snd(tb));
}
};

return go(uncons(ga), uncons(gb));
};


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