# Chapter 38. HTML trees Embedding mini languages within string literals is often not the best way to model a problem domain. An alternative is to leverage the full power of Nim’s syntax. A templating system for convenient HTML tree generation is a good example here. But before we can outline the macro’s design we need to model the HTML tree: type Tag* = enum 1 text, html, head, body, table, tr, th, td TagWithKids = range[html..high(Tag)] 2 HtmlNode* = ref object 3 case tag: Tag of text: s: string else: kids: seq[HtmlNode] proc newTextNode*(s: sink string): HtmlNode = 4 HtmlNode(tag: text, s: s) proc newTree*(tag: TagWithKids; kids: varargs[HtmlNode]): HtmlNode = 5 HtmlNode(tag: tag, kids: @kids) proc add*(parent: HtmlNode; kid: sink HtmlNode) = parent.kids.add kid from std / xmltree import addEscaped proc toString(n: HtmlNode; result: var string) = 6 case n.tag of text: result.addEscaped n.s else: result.add "<" & $n.tag if n.kids.len == 0: 247 result.add " />" else: result.add ">\n" for k in items(n.kids): toString(k, result) result.add "\n" proc `$`*(n: HtmlNode): string = 7 result = newStringOfCap(1000) toString n, result 1 Reduced list of possible HTML tags. 2 A subtype of Tag that covers the tags that have kids. 3 A tree of HTML modelled via a case object. 4 newTextNode constructs a single text node. 5 newTree constructs a node with a variable number of children. 6 toString is recursive and uses a var string parameter as its buffer to write to. This var parameter is crucial for efficiency. 7 For convenience a dollar operator is provided that allocates a large buffer and then calls toString to make effective use of this buffer. newTextNode, newTree, and add are good enough to produce complex HTML tables: proc toTable(headers: openArray[string]; data: seq[seq[int]]): HtmlNode = 1 assert headers.len == data.len 2 var tab = newTree(table) for i in 0...... This style of programming is low level and error prone; it is easy to forget to append row to tab, for example. Instead we would like to write the following: 248 proc toTable(headers: openArray[string]; data: seq[seq[int]]): HtmlNode = assert headers.len == data.len result = buildHtml: body: table: for i in 0..