1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
|
import std/options
import formats/html
# const inline_elements = []
const block_elements = [
"html", "body", "article", "section", "nav", "aside",
"h1", "h2", "h3", "h4", "h5", "h6", "hgroup", "header",
"footer", "address", "p", "hr", "pre", "blockquote",
"ol", "ul", "menu", "li", "dl", "dt", "dd", "figure",
"figcaption", "main", "div", "table", "form", "fieldset",
"legend", "details", "summary"
]
type LayoutKind = enum
Inline, Block, Document
# Improve error messages: suggest a ref object when failing from recursion
type Layout = ref object
case kind*: LayoutKind:
of Inline:
weight: int
of Block:
discard
of Document:
discard
node: Node
parent: Option[Layout]
previous: Option[Layout]
children: seq[Layout]
# x, y, width, height: float
# Recursively construct the layout tree
func layout(node: Node, parent: Option[Layout], previous: Option[Layout]): Layout =
result = Layout() # !!! ref types are nil by default :-(
var children: seq[Layout] = @[]
var prevchild: Option[Layout] = none(Layout)
var kind: LayoutKind = Inline
case node.kind:
of Element:
for child in node.children:
# FIXME: parent nodes are broken. we pass a ref to result, which is different from the returned layout.
let current = child.layout(parent=some(result), previous=prevchild)
children.add(current)
prevchild = some(current)
if kind == Inline and child.kind == Element and child.tag in block_elements:
kind = Block
if node.children.len == 0:
kind = Block
of Text:
discard
return Layout(node: node, parent: parent, previous: previous, children: children, kind: kind)
# Assuming the first node of an HTML object is the <html> tag.
# Right now, HTML generation is Bad so this will change in the future
func layout(html: Html): Layout =
result = Layout(kind: Document, node: html[0], parent: none(Layout), previous: none(Layout), children: @[])
for child in html[0].children:
result.children.add(child.layout(none(Layout), none(Layout)))
# TODO: change Html into a distinct Node and adjust layout accordingly
#[type DocumentLayout = ref object
node: Node
children: seq[Layout]]#
#[func layout_mode(node: Node): LayoutKind =
if node.kind == Text:
return Inline
elif node.children.len > 0:
for child in node.children:
if child.kind == Text: continue
if child.tag in block_elements:
return Block
return Inline
else:
return Block]#
#[func layout(node: Node, parent: Layout, previous: Layout = nil): Layout =
if node.kind == Element and node.children.len > 0:
var previous: Layout = previous
var current: Layout
for child in node.children:
if previous == nil:
current = child.layout(result)
result.children.add(current)
else:
current = child.layout(result, previous)
result.children.add(current)
previous = current
else:
result = Layout(node: node, parent: parent, previous: previous, children: @[])]#
when isMainModule:
import formats/uri, protocols/http, std/strutils, print
proc printLayout(layout: Layout, indentation=0) =
if layout.node.kind == Element:
stdout.write(" ".repeat(indentation))
stdout.write(layout.node.tag)
stdout.write(" ")
stdout.write(layout.kind)
stdout.write("\n")
print layout.parent
for child in layout.children:
child.printLayout(indentation + 2)
let text = parseHTML(httpRequest(parseURL("https://example.org:443/index.html")).body)
print layout(text)
for node in text:
printLayout node.layout(none(Layout), none(Layout))
|