import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Comparator; import java.util.Deque; import java.util.HashSet; import java.util.List; import java.util.Set; public final class DisplayAnOutlineAsANestedTable { public static void main(String[] args) { String 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) 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. """; Node tree = parse(outline); colourTree(tree); String htmlCode = htmlTable(tree); System.out.println(htmlCode); String wikiCode = wikiTable(tree); System.out.println(wikiCode); } // Return the HTML code for the display of the given Node as a table. private static String htmlTable(Node tree) { final int tableColumnCount = tree.colspan(); int rowColumn = 0; StringBuilder builder = new StringBuilder("\n"); // Breadth first traversal of 'tree'. Deque queue = new ArrayDeque(); Set explored = new HashSet(); queue.offer(tree); while ( ! queue.isEmpty() ) { Node currentNode = queue.poll(); if ( explored.contains(currentNode) ) { continue; } if ( rowColumn == 0 ) { builder.append(" \n"); } builder.append(htmlTableData(currentNode)); rowColumn += currentNode.colspan(); if ( rowColumn == tableColumnCount ) { builder.append(" \n"); rowColumn = 0; } for ( Node child : currentNode.children ) { queue.offer(child); } explored.add(currentNode); } builder.append("
\n"); return builder.toString(); } // Return the code for the display of the given Node as a table in Wikipedia. private static String wikiTable(Node tree) { final int tableColumnCount = tree.colspan(); int rowColumn = 0; StringBuilder builder = new StringBuilder(); builder.append( "{| class=\"" + "wikitable" + "\"" + " style=\"" + "text-align: center;" + "\"" + "\n"); // Breadth first traversal of 'tree'. Deque queue = new ArrayDeque(); Set explored = new HashSet(); queue.offer(tree); while ( ! queue.isEmpty() ) { Node currentNode = queue.poll(); if ( explored.contains(currentNode) ) { continue; } if ( rowColumn == 0 ) { builder.append("|-\n"); } builder.append(wikiTableData(currentNode)); rowColumn += currentNode.colspan(); if ( rowColumn == tableColumnCount ) { rowColumn = 0; } for ( Node child : currentNode.children ) { queue.offer(child); } explored.add(currentNode); } builder.append("|}\n"); return builder.toString(); } // Return an HTML table data element for the given Node. private static String htmlTableData(Node node) { String indent = " "; String colspan = " colspan=\"" + node.colspan() + "\""; String style = "style=\"" + "background-color: " + node.colour + "\""; String attributes = colspan + " " + style; return indent + "" + node.text + "\n"; } // Return a Wikipedia table data element for the given Node. private static String wikiTableData(Node node) { if ( node.text.isBlank() ) { return "| |\n"; } String style = "style=\"" + "background: " + node.colour + " \""; String colspan = " colspan=" + node.colspan(); String attributes = style + colspan; return "| " + attributes + " | " + node.text + "\n"; } // Return the given outline as a tree of Node. private static Node parse(String outline) { List tokens = tokenise(outline); Node temporaryTree = new Node("", -1, null); parse(tokens, 0, temporaryTree); Node tree = temporaryTree.children.getFirst(); padTree(tree, tree.height()); return tree; } // Recursively build a tree of Node. private static void parse(List tokens, int index, Node node) { if ( index == tokens.size() ) { return; } Token token = tokens.get(index); if ( token.indent == node.indent ) { // A sibling of node Node current = new Node(token.text, token.indent, node.parent); node.parent.children.addLast(current); parse(tokens, index + 1, current); } else if ( token.indent > node.indent ) { // A child of node Node current = new Node(token.text, token.indent, node); node.children.addLast(current); parse(tokens, index + 1, current); } else if ( token.indent < node.indent ) { // Try the node's parent until a sibling is found parse(tokens, index, node.parent); } } // Pad the tree with blank nodes so that all branches have the same depth. private static void padTree(Node node, int height) { if ( node.isLeaf() && node.depth() < height ) { Node padNode = new Node("", node.indent + 1, node); node.children.addLast(padNode); } for ( Node child : node.children ) { padTree(child, height); } } private static void colourTree(Node node) { if ( node.text.isBlank() ) { node.colour = Colour.blank(); } else if ( node.depth() <= 1 ) { node.colour = Colour.next(); } else { node.colour = node.parent.colour; } for ( Node child : node.children ) { colourTree(child); } } private static List tokenise(String outline) { List tokens = new ArrayList(); for ( String line : outline.split("\n") ) { String lineTrimmed = line.trim(); final int indent = line.length() - lineTrimmed.length(); tokens.addLast( new Token(lineTrimmed, indent) ); } return tokens; } private static final class Node { public Node (String aText, int aIndent, Node aParent) { text = aText; indent = aIndent; parent = aParent; children = new ArrayList(); } public int depth() { return ( parent != null ) ? parent.depth() + 1 : -1; } public int height() { if ( isLeaf() ) { return 0; } return children.stream().map( child -> child.height() ).max(Comparator.naturalOrder()).get() + 1; } public int colspan() { if ( isLeaf() ) { return 1; } return children.stream().map( child -> child.colspan() ).mapToInt(Integer::intValue).sum(); } public boolean isLeaf() { return children.isEmpty(); } private String text; private int indent; private Node parent; private List children; private String colour; } private static final class Colour { public static String next() { index = ( index + 1 ) % colours.size(); return colours.get(index); } public static String blank() { return "#cccccc;"; } private static int index = -1; private static final List colours = List.of( "#ffff66;", "#ffcc66;", "#ccffcc;", "#ccccff;", "#ffcccc;", "#00cccc;", "#cc9966;", "#ffccff;" ); } private record Token(String text, int indent) { } }