# 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" & $n.tag & ">"
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.....