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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
|
import css/cssvalues
import css/selectorparser
import html/dom
# Container to hold a style and a node.
# Pseudo-elements are implemented using StyledNode objects without nodes. Input
# elements are implemented as internal "pseudo-elements."
#
# To avoid having to invalidate the entire tree on pseudo-class changes, each
# node holds a list of nodes their CSS values depend on. (This list may include
# the node itself.) In addition, nodes also store each value valid for
# dependency d. These are then used for checking the validity of StyledNodes.
#
# In other words - say we have to apply the author stylesheets of the following
# document:
#
# <style>
# div:hover { color: red; }
# :not(input:checked) + p { display: none; }
# </style>
# <div>This div turns red on hover.</div>
# <input type=checkbox>
# <p>This paragraph is only shown when the checkbox above is checked.
#
# That produces the following dependency graph (simplified):
# div -> div (hover)
# p -> input (checked)
#
# Then, to check if a node has been invalidated, we just iterate over all
# recorded dependencies of each StyledNode, and check if their registered value
# of the pseudo-class still matches that of its associated element.
#
# So in our example, for div we check if div's :hover pseudo-class has changed,
# for p we check whether input's :checked pseudo-class has changed.
type
StyledType* = enum
stElement, stText, stReplacement
DependencyInfo = array[DependencyType, seq[Element]]
StyledNode* = ref object
parent*: StyledNode
node*: Node
pseudo*: PseudoElem
case t*: StyledType
of stText:
discard
of stElement:
computed*: CSSComputedValues
children*: seq[StyledNode]
# All elements we depend on, for each dependency type d.
depends*: DependencyInfo
of stReplacement:
# replaced elements: quotes, or (TODO) markers, images
content*: CSSContent
template textData*(styledNode: StyledNode): string =
CharacterData(styledNode.node).data
# For debugging
func `$`*(node: StyledNode): string =
if node == nil:
return "nil"
case node.t
of stText:
return "#text " & node.textData
of stElement:
if node.node != nil:
return $node.node
return $node.pseudo
of stReplacement:
return "#replacement"
iterator branch*(node: StyledNode): StyledNode {.inline.} =
var node = node
while node != nil:
yield node
node = node.parent
iterator elementList*(node: StyledNode): StyledNode {.inline.} =
for child in node.children:
yield child
iterator elementList_rev*(node: StyledNode): StyledNode {.inline.} =
for i in countdown(node.children.high, 0):
yield node.children[i]
func findElement*(root: StyledNode; element: Element): StyledNode =
var stack: seq[StyledNode] = @[]
for child in root.elementList_rev:
if child.t == stElement and child.pseudo == peNone:
stack.add(child)
while stack.len > 0:
let node = stack.pop()
if node.node == element:
return node
for child in node.elementList_rev:
if child.t == stElement and child.pseudo == peNone:
stack.add(child)
func isDomElement*(styledNode: StyledNode): bool {.inline.} =
styledNode.t == stElement and styledNode.pseudo == peNone
# DOM-style getters, for Element interoperability...
func parentElement*(node: StyledNode): StyledNode {.inline.} =
node.parent
proc isValid*(styledNode: StyledNode; toReset: var seq[Element]): bool =
if styledNode.t in {stText, stReplacement}:
return true
if styledNode.node != nil:
let element = Element(styledNode.node)
if element.invalid:
toReset.add(element)
return false
for d in DependencyType:
for dep in styledNode.depends[d]:
if d in dep.invalidDeps:
toReset.add(dep)
return false
return true
proc addDependency*(styledNode: StyledNode; dep: Element; t: DependencyType) =
if dep notin styledNode.depends[t]:
styledNode.depends[t].add(dep)
func newStyledElement*(parent: StyledNode; element: Element): StyledNode =
return StyledNode(t: stElement, node: element, parent: parent)
# Root
func newStyledElement*(element: Element): StyledNode =
return StyledNode(t: stElement, node: element)
func newStyledElement*(parent: StyledNode; pseudo: PseudoElem;
computed: CSSComputedValues): StyledNode =
return StyledNode(
t: stElement,
computed: computed,
pseudo: pseudo,
parent: parent
)
func newStyledText*(parent: StyledNode; text: Text): StyledNode =
return StyledNode(t: stText, node: text, parent: parent)
func newStyledText*(text: string): StyledNode =
return StyledNode(t: stText, node: CharacterData(data: text))
func newStyledReplacement*(parent: StyledNode; content: CSSContent;
pseudo: PseudoElem): StyledNode =
return StyledNode(
t: stReplacement,
parent: parent,
content: content,
pseudo: pseudo
)
|