1
1
import LiveViewNativeCore
2
2
3
+ /// Raised when a `Document` fails to parse
3
4
public struct ParseError : Error {
4
5
let message : String
5
6
@@ -8,8 +9,26 @@ public struct ParseError: Error {
8
9
}
9
10
}
10
11
12
+ /// Represents the various types of events that a `Document` can produce
13
+ public enum EventType {
14
+ /// When a document is modified in some way, the `changed` event is raised
15
+ case changed
16
+ }
17
+
18
+ struct Handler {
19
+ let context : AnyObject ?
20
+ let callback : ( Document , AnyObject ? ) -> ( )
21
+
22
+ func call( _ doc: Document ) {
23
+ callback ( doc, context)
24
+ }
25
+ }
26
+
27
+ /// A `Document` corresponds to the tree of elements in a UI, and supports a variety
28
+ /// of operations used to traverse, query, and mutate that tree.
11
29
public class Document {
12
30
var repr : __Document
31
+ var handlers : [ EventType : Handler ] = [ : ]
13
32
14
33
init ( _ doc: __Document ) {
15
34
self . repr = doc
@@ -18,9 +37,19 @@ public class Document {
18
37
deinit {
19
38
__liveview_native_core $Document$drop( self . repr)
20
39
}
21
- }
22
40
23
- extension Document {
41
+ /// Parse a `Document` from the given `String` or `String`-like type
42
+ ///
43
+ /// The given text should be a valid HTML-ish document, insofar that the structure should
44
+ /// be that of an HTML document, but the tags, attributes, and their usages do not have to
45
+ /// be valid according to the HTML spec.
46
+ ///
47
+ /// - Parameters:
48
+ /// - str: The string to parse
49
+ ///
50
+ /// - Returns: A document representing the parsed content
51
+ ///
52
+ /// - Throws: `ParseError` if the content cannot be parsed for some reason
24
53
public static func parse< S: ToRustStr > ( _ str: S ) throws -> Document {
25
54
try str. toRustStr ( { rustStr -> Result < Document , ParseError > in
26
55
let errorPtr = UnsafeMutableRawPointer . allocate ( byteCount: MemoryLayout< _RustString> . stride, alignment: MemoryLayout< _RustString> . alignment) . assumingMemoryBound ( to: _RustString. self)
@@ -36,14 +65,44 @@ extension Document {
36
65
} ) . get ( )
37
66
}
38
67
39
- public func merge( with doc: Document ) -> Bool {
40
- return __liveview_native_core $Document$merge( self . repr, doc. repr)
68
+ /// Register a callback to be fired when a matching event occurs on this document.
69
+ ///
70
+ /// The given callback receives the document to which the event applies, as well as the
71
+ /// (optional) context object provided.
72
+ ///
73
+ /// Only one callback per event type is supported. Calling this function multiple times for the
74
+ /// same event will only result in the last callback provided being invoked for that tevent
75
+ ///
76
+ /// - Parameters:
77
+ /// - event: The `EventType` for which the given callback should be invoked
78
+ /// - context: A caller-provided value which should be passed to the callback when it is invoked
79
+ /// - callback: The callback to invoke when an event of the given type occurs
80
+ ///
81
+ public func on( _ event: EventType , with context: AnyObject ? , _ callback: @escaping ( Document , AnyObject ? ) -> ( ) ) {
82
+ self . handlers [ event] = Handler ( context: context, callback: callback)
83
+ }
84
+
85
+ /// Updates this document by calculating the changes needed to make it equivalent to `doc`,
86
+ /// and then applying those changes.
87
+ ///
88
+ /// - Parameters:
89
+ /// - doc: The document to compare against
90
+ public func merge( with doc: Document ) {
91
+ if __liveview_native_core $Document$merge( self . repr, doc. repr) {
92
+ if let handler = self . handlers [ . changed] {
93
+ handler. call ( self )
94
+ }
95
+ }
41
96
}
42
97
98
+ /// Returns a reference to the root node of the document
99
+ ///
100
+ /// The root node is not part of the document itself, but can be used to traverse the document tree top-to-bottom.
43
101
public func root( ) -> NodeRef {
44
102
return __liveview_native_core $Document$root( self . repr)
45
103
}
46
104
105
+ /// Enables indexing of the document by node reference, returning the reified `Node` to which it corresponds
47
106
public subscript( ref: NodeRef ) -> Node {
48
107
let node = __liveview_native_core $Document$get( self . repr, ref)
49
108
return Node ( doc: self , ref: ref, data: node)
@@ -65,16 +124,33 @@ extension Document {
65
124
}
66
125
}
67
126
127
+ /// Represents a node in the document tree
128
+ ///
129
+ /// A node can be one of three types:
130
+ ///
131
+ /// - A root node, which is a special marker node for the root of the document
132
+ /// - A leaf node, which is simply text content, cannot have children or attributes
133
+ /// - An element node, which can have children and attributes
134
+ ///
135
+ /// A node in a document is uniquely identified by a `NodeRef` for the lifetime of
136
+ /// that node in the document. But a `NodeRef` is not a stable identifier when the
137
+ /// tree is modified. In some cases the `NodeRef` remains the same while the content
138
+ /// changes, and in others, a new node is allocated, so a new `NodeRef` is used.
68
139
public class Node : Identifiable {
140
+ /// The type and associated data of this node
69
141
public enum Data {
70
- case Root
71
- case Element ( HTMLElement )
72
- case Leaf ( String )
142
+ case root
143
+ case element ( ElementNode )
144
+ case leaf ( String )
73
145
}
74
146
75
147
let doc : Document
148
+
149
+ /// The identifier for this node in its `Document`
76
150
public let id : NodeRef
151
+ /// The type and data associated with this node
77
152
public let data : Data
153
+ /// The attributes associated with this node
78
154
public lazy var attributes : [ Attribute ] = {
79
155
let refs = doc. getAttrs ( id)
80
156
var attributes : [ Attribute ] = [ ]
@@ -89,14 +165,15 @@ public class Node: Identifiable {
89
165
self . doc = doc
90
166
switch data. ty {
91
167
case . NodeTypeRoot:
92
- self . data = . Root
168
+ self . data = . root
93
169
case . NodeTypeElement:
94
- self . data = . Element ( HTMLElement ( doc: doc, ref: ref, data: data. data. element) )
170
+ self . data = . element ( ElementNode ( doc: doc, ref: ref, data: data. data. element) )
95
171
case . NodeTypeLeaf:
96
- self . data = . Leaf ( RustStr ( data. data. leaf) . toString ( ) !)
172
+ self . data = . leaf ( RustStr ( data. data. leaf) . toString ( ) !)
97
173
}
98
174
}
99
175
176
+ /// Nodes are indexable by attribute name, returning the first attribute with that name
100
177
public subscript( _ name: AttributeName ) -> Attribute ? {
101
178
for attr in attributes {
102
179
if attr. name == name {
@@ -113,6 +190,7 @@ extension Node: Sequence {
113
190
}
114
191
}
115
192
193
+ /// An iterator for the direct children of a `Node`
116
194
public struct NodeChildrenIterator : IteratorProtocol {
117
195
let doc : Document
118
196
let children : RustSlice < NodeRef >
@@ -133,9 +211,13 @@ public struct NodeChildrenIterator: IteratorProtocol {
133
211
}
134
212
}
135
213
136
- public struct HTMLElement {
214
+ /// Represents a node in a `Document` which can have children and attributes
215
+ public struct ElementNode {
216
+ /// An (optional) namespace for the element tag name
137
217
public let namespace : String ?
218
+ /// The name of the element tag in the document
138
219
public let tag : String
220
+ /// A dictionary of attributes associated with this element
139
221
public let attributes : [ AttributeName : String ]
140
222
141
223
init ( doc: Document , ref: NodeRef , data: __Element ) {
@@ -151,8 +233,11 @@ public struct HTMLElement {
151
233
}
152
234
}
153
235
236
+ /// An attribute is a named string value associated with an element
154
237
public struct Attribute {
238
+ /// The fully-qualified name of the attribute
155
239
public var name : AttributeName
240
+ /// The value of this attribute, if there was one
156
241
public var value : String ?
157
242
158
243
init ( name: AttributeName , value: String ? ) {
@@ -183,6 +268,10 @@ extension Attribute: Hashable {
183
268
}
184
269
}
185
270
271
+ /// Represents a fully-qualified attribute name
272
+ ///
273
+ /// Attribute names can be namespaced, so rather than represent them as a plain `String`,
274
+ /// we use this type to preserve the information for easy accessibility.
186
275
public struct AttributeName {
187
276
public var namespace : String ?
188
277
public var name : String
0 commit comments