-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathnode.go
344 lines (279 loc) · 7.71 KB
/
node.go
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
package prosemirror
import (
"fmt"
"maps"
"slices"
"strings"
"github.com/go-json-experiment/json"
)
// A node is a nested object which represents a fragment of a document. It roughly corresponds to an HTML element.
// Each node type has an associated string, known as its name.
// It may have any number of attributes, represented as a map from strings to arbitrary values.
// - If the node type is text, it may have a string content property.
// - If the node type is a non-leaf node, it may have a content property containing an array of child nodes.
// - If the node type has inline content, it may have a marks property containing an array of marks.
// - If the node type is a block node, it may have a attrs property containing a map of additional HTML attributes for the element.
//
// The size of a node is either n.textLen(), 1, or the sum of the sizes of its children.
type Node struct {
// The type of this node.
Type NodeType `json:"type,omitempty"`
// A map of attribute names to values.
// The kind of attributes depends on the node type.
Attrs map[string]any `json:"attrs,omitempty"`
// The marks (things like whether it is emphasized or part of a link) applied to this node.
Marks []Mark `json:"marks,omitempty"`
// For text nodes, this contains the node's text content.
Text string `json:"text,omitempty"`
// The child node at the given index.
Content Fragment `json:"content,omitempty,omitzero"`
}
func (n Node) Replace(from, to int, s Slice) (Node, error) {
fromNode, err := resolve(n, from)
if err != nil {
return Node{}, fmt.Errorf("error resolving from: %w", err)
}
toNode, err := resolve(n, to)
if err != nil {
return Node{}, fmt.Errorf("error resolving to: %w", err)
}
return replace(fromNode, toNode, s)
}
func (n Node) Resolve(pos int) (ResolvedPos, error) {
return resolve(n, pos)
}
func (n Node) eq(other Node) bool {
if n.Type.isText() {
return n.sameMarkup(other) && n.Text == other.Text
}
return n.Type.Eq(other.Type) &&
maps.Equal(n.Attrs, other.Attrs) &&
slices.EqualFunc(n.Marks, other.Marks, func(a, b Mark) bool {
return a.Eq(b)
}) &&
n.Text == other.Text &&
slices.EqualFunc(n.Content.Content, other.Content.Content, func(a, b Node) bool {
return a.eq(b)
})
}
func (n Node) String() string {
b := &strings.Builder{}
b.WriteString("Node{")
fmt.Fprintf(b, "Type: %s", n.Type)
if len(n.Attrs) > 0 {
fmt.Fprintf(b, ", Attrs: %v", n.Attrs)
}
if len(n.Marks) > 0 {
fmt.Fprintf(b, ", Marks: %v", n.Marks)
}
if n.Text != "" {
fmt.Fprintf(b, ", Text: %q", n.Text)
}
if n.Content.Size > 0 {
fmt.Fprintf(b, ", Content: %v", n.Content)
}
b.WriteString("}")
return b.String()
}
func (n Node) NodeSize() int {
if n.IsText() {
return n.textLen()
}
if n.IsLeaf() {
return 1
}
return 2 + n.Content.Size
}
func (n Node) Child(index int) *Node {
return n.Content.Child(index)
}
func (n Node) MaybeChild(index int) *Node {
return n.Content.maybeChild(index)
}
func (n Node) FirstChild() *Node {
return n.Content.firstChild()
}
func (n Node) IsText() bool {
return n.Type.isText()
}
func (n Node) IsLeaf() bool {
return n.Type.isLeaf()
}
func (n Node) IsAtom() bool {
return n.Type.isAtom()
}
func (n Node) IsInline() bool {
return n.Type.isInline()
}
func (n Node) WithMarks(m []Mark) Node {
n = n.Clone()
n.Marks = m
return n
}
// Cut out the part of the document between the given positions, and
// return it as a `Slice` object.
func (n Node) Slice(from, to int, includeParents bool) (Slice, error) {
if to == -1 {
to = n.Content.Size
}
if from == to {
return Slice{}, nil
}
fromNode, err := n.Resolve(from)
if err != nil {
return Slice{}, fmt.Errorf("error resolving from: %w", err)
}
toNode, err := n.Resolve(to)
if err != nil {
return Slice{}, fmt.Errorf("error resolving to: %w", err)
}
depth := 0
if !includeParents {
depth = fromNode.SharedDepth(to)
}
start := fromNode.Start(depth)
node := fromNode.Node(depth)
content := node.Content.cut(fromNode.Pos-start, toNode.Pos-start)
return Slice{
Content: content,
OpenStart: fromNode.Depth - depth,
OpenEnd: toNode.Depth - depth,
}, nil
}
func (n Node) close(f Fragment) (Node, error) {
if err := n.Type.CheckContent(f); err != nil {
return Node{}, err
}
return n.copy(f), nil
}
func (n Node) ChildCount() int {
return n.Content.ChildCount()
}
func (n Node) MarshalJSON() ([]byte, error) {
type StringNode struct {
Type NodeType `json:"type"`
Attrs map[string]any `json:"attrs,omitempty"`
Marks []Mark `json:"marks,omitempty"`
Text string `json:"text"`
}
type FragmentNode struct {
Type NodeType `json:"type"`
Attrs map[string]any `json:"attrs,omitempty"`
Marks []Mark `json:"marks,omitempty"`
Content Fragment `json:"content,omitempty,omitzero"`
}
if n.Type.isText() {
return json.Marshal(StringNode{
Type: n.Type,
Attrs: n.Attrs,
Marks: n.Marks,
Text: n.Text,
})
}
return json.Marshal(FragmentNode{
Type: n.Type,
Attrs: n.Attrs,
Marks: n.Marks,
Content: n.Content,
})
}
// Test whether replacing the range between `from` and `to` (by
// child index) with the given replacement fragment (which defaults
// to the empty fragment) would leave the node's content valid. You
// can optionally pass `start` and `end` indices into the
// replacement fragment.
func (n Node) canReplace(from, to int, replacement Fragment, start, end int) bool {
if start == -1 {
start = 0
}
if end == -1 {
end = replacement.ChildCount()
}
one := n.contentMatchAt(from).matchFragment(replacement, start, end)
if one == nil {
return false
}
two := one.matchFragment(n.Content, to, -1)
if two == nil || !two.ValidEnd {
return false
}
for i := start; i < end; i++ {
if err := n.Type.validMarks(replacement.Child(i).Marks); err != nil {
return false
}
}
return true
}
func (n Node) contentMatchAt(index int) *ContentMatch {
return n.Type.ContentMatch.matchFragment(n.Content, 0, index)
}
func (n Node) cut(from int, to int) Node {
if n.IsText() {
if to == -1 {
to = n.textLen()
}
if from == 0 && to == n.textLen() {
return n
}
return n.withText(n.Text[from:to])
}
if to == -1 {
to = n.Content.Size
}
if from == 0 && to == n.Content.Size {
return n
}
return n.copy(n.Content.cut(from, to))
}
func (n Node) withText(s string) Node {
if n.Text == s {
return n
}
return Node{
Type: n.Type,
Text: s,
Attrs: maps.Clone(n.Attrs),
Marks: slices.Clone(n.Marks),
Content: n.Content.clone(),
}
}
func (n Node) copy(f Fragment) Node {
return Node{
Type: n.Type,
Text: n.Text,
Attrs: maps.Clone(n.Attrs),
Marks: slices.Clone(n.Marks),
Content: f.clone(),
}
}
func (n Node) Clone() Node {
return Node{
Type: n.Type,
Attrs: maps.Clone(n.Attrs),
Marks: slices.Clone(n.Marks),
Text: n.Text,
Content: n.Content.clone(),
}
}
func (n Node) hasMarkup(t NodeType, attrs map[string]any, marks []Mark) bool {
return n.Type.Eq(t) &&
maps.Equal(n.Attrs, attrs) &&
slices.EqualFunc(n.Marks, marks, func(a, b Mark) bool {
return a.Eq(b)
})
}
func (n Node) sameMarkup(other Node) bool {
return n.hasMarkup(other.Type, other.Attrs, other.Marks)
}
// Call the given callback for every descendant node. Doesn't
// descend into a node when the callback returns `false`.
func (n Node) Descendants(f func(n Node, pos int, parent *Node, index int) bool) {
n.nodesBetween(0, n.Content.Size, f, 0)
}
func (n Node) nodesBetween(from int, to int, f func(node Node, start int, parent *Node, index int) bool, startPos int) {
n.Content.nodesBetween(from, to, f, startPos, &n)
}
// Javascript uses UTF-16 for code points, so I gotta find the length of a string in UTF-16 bytes, not runes.
func (n Node) textLen() int {
return utf16Len(n.Text)
}