mdom 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/mdom/node.rb CHANGED
@@ -1,254 +1,732 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Markdown DOM:把 Markdown 文本解析为语法树(AST)的顶层命名空间。
4
+ require_relative "hooks"
5
+
3
6
  module MDOM
4
- # Base class for every node in the Markdown tree.
7
+ # Markdown 语法树的节点定义。
5
8
  #
6
- # A node carries a +type+ (a Symbol), an ordered list of +children+, and
7
- # optional +attributes+ (a Hash). The +parent+ of a node is maintained
8
- # automatically whenever a child is appended or removed.
9
- class Node
10
- attr_reader :type, :children, :attributes
11
- attr_accessor :parent
12
-
13
- # -- construction -------------------------------------------------------
14
-
15
- def initialize(type, attributes: {}, children: [])
16
- @type = type
17
- @attributes = attributes
18
- @children = []
19
- @parent = nil
20
- children.each { |child| append(child) }
21
- end
9
+ # 本模块把节点能力按职责拆成若干 mixin——挂载(+Attaching+)、摘除
10
+ # (+Detaching+)、替换(+Replacing+)、重排(+Reordering+)、遍历
11
+ # (+Traversal+)、导航(+Navigation+)、判定(+Predicates+)、深度维护
12
+ # (+Depth+)——由 +BaseNode+ 统一混入;具体节点类型(段落、标题、列表……)
13
+ # 继承 +BaseNode+,只需声明自己的 +type+ 与语义。
14
+ #
15
+ # 命名遵循 Ruby 核心类(+Array+ / +Hash+)的惯例:消息名描述*接收者*做什么,
16
+ # 被操作的对象由参数表达,因此没有 +remove_child+ / +insert_child_at+ 这类
17
+ # 把宾语写进方法名的形式。
18
+ #
19
+ # 领域错误见 +Error+ 及其子类 +RootError+、+BoundaryError+、+CycleError+。
20
+ module Node
21
+ # 节点操作的错误基类。+MDOM::Node+ 里抛出的领域错误都继承它,调用方可以
22
+ # 只 rescue 一次(+MDOM::Node::Error+)覆盖全部。
23
+ class Error < StandardError; end
24
+
25
+ # 对根节点做了它没有意义的操作(根节点没有父节点,也就没有位置、兄弟)。
26
+ class RootError < Error; end
27
+
28
+ # 越过了兄弟边界:第一个子节点没有前一个兄弟,最后一个子节点没有后一个兄弟。
29
+ class BoundaryError < Error; end
30
+
31
+ # 试图把节点挂到自身或它的祖先之下,会形成环。
32
+ class CycleError < Error; end
33
+
34
+ # 挂载子节点:追加、前插、插入指定位置。
35
+ #
36
+ # 这些方法只负责"位置"这一半;"改换门庭"由被挂载的节点自己完成——调用
37
+ # +node.attach_to!+(参数是本节点)让 +node+ 自行脱离原父并认本节点为父,
38
+ # 本节点不去直接改写 +node+ 的状态。
39
+ module Attaching
40
+ # 把 +node+ 追加到子列表的末尾。
41
+ #
42
+ # 若 +node+ 原本挂在另一个父节点下,+node+ 会先脱离原来的父节点,从而
43
+ # 维持“一个子节点只属于一个父节点”的不变式。
44
+ #
45
+ # 参数:
46
+ # * +node+ —— 要追加的节点(+BaseNode+)。
47
+ #
48
+ # 返回值:+self+,便于链式调用。
49
+ #
50
+ # 抛出:
51
+ # * +ArgumentError+ —— +node+ 为 +nil+。
52
+ # * +CycleError+ —— +node+ 是 +self+ 自身或其祖先(挂接会形成环)。
53
+ def <<(node)
54
+ assert_attachable!(node)
55
+ node.attach_to!(self)
56
+ children << node
57
+ self
58
+ end
22
59
 
23
- # -- children -----------------------------------------------------------
60
+ # 依次追加多个节点。
61
+ #
62
+ # 等价于对每个参数调用 #<<,并保留参数给出的顺序。
63
+ #
64
+ # 参数:
65
+ # * +nodes+ —— 零个或多个要追加的节点(+BaseNode+)。
66
+ #
67
+ # 返回值:+self+,便于链式调用。
68
+ #
69
+ # 抛出:
70
+ # * +ArgumentError+ —— 任一节点为 +nil+。
71
+ # * +CycleError+ —— 某个节点是 +self+ 自身或其祖先。
72
+ def append(*nodes)
73
+ nodes.each { |node| self << node }
74
+ self
75
+ end
24
76
 
25
- # Append +child+ to the end of this node's children. Reparents +child+
26
- # away from any previous parent. Returns +self+ so it can be chained.
27
- def append(child)
28
- return self if child.nil?
77
+ # 把 +node+ 插到子列表的最前面。
78
+ #
79
+ # 参数:
80
+ # * +node+ —— 要插入的节点(+BaseNode+)。
81
+ #
82
+ # 返回值:+self+,便于链式调用。
83
+ #
84
+ # 抛出:
85
+ # * +ArgumentError+ —— +node+ 为 +nil+。
86
+ # * +CycleError+ —— +node+ 是 +self+ 自身或其祖先。
87
+ def prepend(node)
88
+ assert_attachable!(node)
89
+ node.attach_to!(self)
90
+ children.unshift(node)
91
+ self
92
+ end
29
93
 
30
- child.remove if child.parent
31
- child.parent = self
32
- @children << child
33
- self
34
- end
35
- alias << append
94
+ # 在 +index+ 位置插入 +node+。
95
+ #
96
+ # 参数顺序与 Array#insert 一致(位置在前、元素在后),索引惯例也相同:
97
+ #
98
+ # * 非负值:直接作为插入位置,+0+ 表示插到最前面,+children.size+ 表示
99
+ # 追加到末尾;
100
+ # * 负值:从末尾往前计数,+-1+ 表示追加到末尾(等价于 #<<),+-2+ 表示
101
+ # 插到最后一个子节点之前。
102
+ #
103
+ # 合法取值为 +-children.size-1+ 到 +children.size+;超出该范围会抛出 +IndexError+,
104
+ # 而不是像 Array#insert 那样在右侧补 +nil+(+nil+ 不是合法的子节点)。
105
+ #
106
+ # 参数:
107
+ # * +index+ —— 插入位置(+Integer+,可为负),取值 +-children.size-1+
108
+ # 到 +children.size+。
109
+ # * +node+ —— 要插入的节点(+BaseNode+)。
110
+ #
111
+ # 返回值:+self+,便于链式调用。
112
+ #
113
+ # 抛出:
114
+ # * +TypeError+ —— +index+ 不是 +Integer+。
115
+ # * +IndexError+ —— +index+ 超出合法范围。
116
+ # * +ArgumentError+ —— +node+ 为 +nil+。
117
+ # * +CycleError+ —— +node+ 是 +self+ 自身或其祖先。
118
+ def insert(index, node)
119
+ raise TypeError, "index must be an Integer" unless index.is_a?(Integer)
120
+
121
+ assert_attachable!(node)
122
+ node.unlink! # 先让 node 脱离,尺寸与下标才有确定含义
123
+ check_index!(index)
124
+ node.attach_to!(self)
125
+ children.insert(index, node)
126
+ self
127
+ end
36
128
 
37
- # Yield each child in order. Returns an Enumerator when no block is given.
38
- def each_child(&block)
39
- return enum_for(:each_child) unless block
129
+ # 把 +node+ 插到 +relative+(本节点的一个直接子节点)之前。
130
+ #
131
+ # +relative+ 必须已经是本节点的直接子节点,否则抛出 +ArgumentError+。
132
+ # 若 +node+ 本来就是本节点的子节点,会先让它脱离、再重新定位 +relative+,
133
+ # 因此不会因为下标左移而插错位置。
134
+ #
135
+ # 参数:
136
+ # * +node+ —— 要插入的节点(+BaseNode+)。
137
+ # * +relative+ —— 作为插入锚点的直接子节点(+BaseNode+),不能为 +nil+。
138
+ #
139
+ # 返回值:+self+,便于链式调用。
140
+ #
141
+ # 抛出:
142
+ # * +ArgumentError+ —— +relative+ 为 +nil+,或不是本节点的直接子节点。
143
+ # * +ArgumentError+ —— +node+ 与 +relative+ 是同一个节点。
144
+ # * +ArgumentError+ —— +node+ 为 +nil+。
145
+ # * +CycleError+ —— +node+ 是 +self+ 自身或其祖先。
146
+ def insert_before(node, relative)
147
+ raise ArgumentError, "relative cannot be nil" if relative.nil?
148
+ raise ArgumentError, "relative is not a child of this node" unless index(relative)
149
+ raise ArgumentError, "node and relative must be different nodes" if node.equal?(relative)
150
+
151
+ assert_attachable!(node)
152
+ node.unlink!
153
+ insert(index(relative), node)
154
+ end
40
155
 
41
- @children.each(&block)
42
- self
156
+ # 把 +node+ 插到 +relative+(本节点的一个直接子节点)之后。
157
+ #
158
+ # +relative+ 必须已经是本节点的直接子节点,否则抛出 +ArgumentError+。
159
+ # 若 +relative+ 是最后一个子节点,+node+ 会被追加到末尾。
160
+ #
161
+ # 参数:
162
+ # * +node+ —— 要插入的节点(+BaseNode+)。
163
+ # * +relative+ —— 作为插入锚点的直接子节点(+BaseNode+),不能为 +nil+。
164
+ #
165
+ # 返回值:+self+,便于链式调用。
166
+ #
167
+ # 抛出:
168
+ # * +ArgumentError+ —— +relative+ 为 +nil+,或不是本节点的直接子节点。
169
+ # * +ArgumentError+ —— +node+ 与 +relative+ 是同一个节点。
170
+ # * +ArgumentError+ —— +node+ 为 +nil+。
171
+ # * +CycleError+ —— +node+ 是 +self+ 自身或其祖先。
172
+ def insert_after(node, relative)
173
+ raise ArgumentError, "relative cannot be nil" if relative.nil?
174
+ raise ArgumentError, "relative is not a child of this node" unless index(relative)
175
+ raise ArgumentError, "node and relative must be different nodes" if node.equal?(relative)
176
+
177
+ assert_attachable!(node)
178
+ node.unlink!
179
+ insert(index(relative) + 1, node)
180
+ end
43
181
  end
44
- alias each each_child
45
182
 
46
- def children?
47
- !@children.empty?
48
- end
183
+ # 摘除子节点。
184
+ module Detaching
185
+ # 把直接子节点 +node+ 摘除。
186
+ #
187
+ # 当 +node+ 不是本节点的直接子节点(或为 +nil+)时不做任何事。摘除成功后,
188
+ # +node+ 的 +parent+ 被置为 +nil+,其子树的 +depth+ 也会重新计算。
189
+ #
190
+ # 参数:
191
+ # * +node+ —— 要摘除的子节点(+BaseNode+ 或 +nil+)。
192
+ #
193
+ # 返回值:+self+,便于链式调用。
194
+ def remove(node)
195
+ return self if node.nil?
196
+
197
+ node.unlink! if children.delete(node)
198
+ self
199
+ end
200
+
201
+ # 摘除并返回位于 +index+ 的子节点。
202
+ #
203
+ # +index+ 遵循 Array#delete_at 的索引惯例:非负值直接作为位置,负值
204
+ # 从末尾往前计数(+-1+ 表示最后一个子节点)。越界时返回 +nil+。
205
+ #
206
+ # 参数:
207
+ # * +index+ —— 子节点下标(+Integer+,可为负)。
208
+ #
209
+ # 返回值:被摘除的子节点;+index+ 越界时返回 +nil+。
210
+ #
211
+ # 抛出:+TypeError+,当 +index+ 不是 +Integer+ 时(例如传入 Range)。
212
+ #
213
+ # 注意:本方法只从 +children+ 中移除节点,不会重置它的 +parent+ 指针;
214
+ # 若需要该节点完整脱离(+parent+ 也置空、+depth+ 重算),请对返回值
215
+ # 再调用 #unlink!。
216
+ def extract_at(index)
217
+ raise TypeError, "index must be an Integer" unless index.is_a?(Integer)
218
+
219
+ children.delete_at(index)
220
+ end
49
221
 
50
- # Remove this node from its current parent, if any. Returns +self+.
51
- def remove
52
- @parent&.children&.delete(self)
53
- @parent = nil
54
- self
222
+ # 把本节点从其父节点上完整摘除,并重置本子树的 +depth+。
223
+ #
224
+ # 摘除是双向的:既从父节点的 +children+ 中删除自己,也把自己的 +parent+
225
+ # 置为 +nil+。若本节点已是根(+parent+ 为 +nil+),则只重算 +depth+。
226
+ #
227
+ # 返回值:+self+,便于链式调用。
228
+ def unlink!
229
+ @parent&.children&.delete(self)
230
+ @parent = nil
231
+ sync_depth!
232
+ self
233
+ end
55
234
  end
56
235
 
57
- # -- tree navigation ----------------------------------------------------
236
+ # 替换子节点,或替换、包装节点自身。
237
+ module Replacing
238
+ # 用 +replacement+ 顶替本节点的一个直接子节点 +child+。
239
+ #
240
+ # +child+ 与 +replacement+ 都会先从各自的原父节点脱离,然后由
241
+ # +replacement+ 占据 +child+ 原来的位置,从而保持兄弟节点的相对顺序。
242
+ #
243
+ # 参数:
244
+ # * +child+ —— 要被顶替的直接子节点(+BaseNode+)。
245
+ # * +replacement+ —— 替换用的新节点(+BaseNode+)。
246
+ #
247
+ # 返回值:+self+,便于链式调用。当 +child+ 不是本节点的直接子节点,
248
+ # 或 +replacement+ 就是 +child+ 本身时,不做任何改动并返回 +self+。
249
+ #
250
+ # 抛出:
251
+ # * +ArgumentError+ —— +replacement+ 为 +nil+。
252
+ # * +CycleError+ —— +replacement+ 是 +self+ 自身或其祖先。
253
+ def replace(child, replacement)
254
+ idx = index(child)
255
+ return self if idx.nil? || replacement.equal?(child)
256
+
257
+ assert_attachable!(replacement)
258
+ replacement.unlink!
259
+ idx = index(child)
260
+ return self unless idx
261
+
262
+ child.unlink!
263
+ replacement.attach_to!(self)
264
+ children.insert(idx, replacement)
265
+ self
266
+ end
267
+
268
+ # 在父节点中,把本节点替换为 +node+。
269
+ #
270
+ # 内部转发给父节点的 #replace。
271
+ #
272
+ # 参数:
273
+ # * +node+ —— 用于替换的新节点(+BaseNode+)。
274
+ #
275
+ # 返回值:+node+(注意:不是 +self+)。
276
+ #
277
+ # 抛出:
278
+ # * +RootError+ —— 本节点是根节点(没有父节点),无法被替换。
279
+ # * +ArgumentError+ —— +node+ 为 +nil+。
280
+ # * +CycleError+ —— +node+ 是本节点的祖先。
281
+ def replace_with(node)
282
+ raise RootError, "cannot replace the root node" unless @parent
283
+
284
+ @parent.replace(self, node)
285
+ node
286
+ end
58
287
 
59
- # The topmost ancestor (a node whose parent is nil).
60
- def root
61
- node = self
62
- node = node.parent while node.parent
63
- node
288
+ # 用 +wrapper+ 把本节点包装起来。
289
+ #
290
+ # 包装后:+wrapper+ 占据本节点原来的位置,而本节点成为 +wrapper+ 的
291
+ # 子节点(唯一子节点)。
292
+ #
293
+ # 参数:
294
+ # * +wrapper+ —— 包装节点(+BaseNode+),不能为 +nil+。
295
+ #
296
+ # 返回值:+wrapper+(注意:不是 +self+)。
297
+ #
298
+ # 抛出:
299
+ # * +RootError+ —— 本节点是根节点,无法被包装。
300
+ # * +ArgumentError+ —— +wrapper+ 为 +nil+。
301
+ # * +CycleError+ —— +wrapper+ 是本节点自身或其祖先。
302
+ def wrap_with(wrapper)
303
+ raise RootError, "cannot wrap the root node" unless @parent
304
+ raise ArgumentError, "wrapper cannot be nil" if wrapper.nil?
305
+
306
+ @parent.replace(self, wrapper) # wrapper 顶替本节点原来的位置
307
+ wrapper << self
308
+ wrapper
309
+ end
64
310
  end
65
311
 
66
- # Depth in the tree: 0 at the root, 1 for its direct children, etc.
67
- def depth
68
- return 0 if @parent.nil?
312
+ # 在同级子节点之间调换位置。
313
+ module Reordering
314
+ # 把本节点与它前一个兄弟节点交换位置。
315
+ #
316
+ # 返回值:+self+,便于链式调用。
317
+ #
318
+ # 抛出:
319
+ # * +RootError+ —— 本节点是根节点。
320
+ # * +BoundaryError+ —— 本节点已经是第一个子节点。
321
+ def slide_prev
322
+ raise RootError, "cannot slide the root node" unless @parent
323
+
324
+ idx = position
325
+ raise BoundaryError, "the first child cannot slide prev" if idx.zero?
326
+
327
+ @parent.swap_children(idx - 1, idx)
328
+ self
329
+ end
69
330
 
70
- @parent.depth + 1
331
+ # 把本节点与它后一个兄弟节点交换位置。
332
+ #
333
+ # 返回值:+self+,便于链式调用。
334
+ #
335
+ # 抛出:
336
+ # * +RootError+ —— 本节点是根节点。
337
+ # * +BoundaryError+ —— 本节点已经是最后一个子节点。
338
+ def slide_next
339
+ raise RootError, "cannot slide the root node" unless @parent
340
+
341
+ idx = position
342
+ raise BoundaryError, "the last child cannot slide next" if idx == @parent.children.size - 1
343
+
344
+ @parent.swap_children(idx, idx + 1)
345
+ self
346
+ end
71
347
  end
72
348
 
73
- # The chain of ancestors, closest first. Returns [] for a root node.
74
- def ancestors
75
- list = []
76
- node = @parent
77
- while node
78
- list << node
79
- node = node.parent
349
+ # 深度优先遍历。
350
+ module Traversal
351
+ # 深度优先(先序)遍历本节点及其全部后代。
352
+ #
353
+ # 遍历顺序:先产出节点自身,再依次遍历各子树。
354
+ #
355
+ # 由于 +BaseNode+ 混入了 +Enumerable+,而 +Enumerable+ 的 +map+ /
356
+ # +select+ / +count+ / +to_a+ 等方法都建立在 +each+ 之上,本方法产出的是
357
+ # 「整棵子树(含自身)」,这一点与“只遍历直接子节点”的数组直觉不同;
358
+ # 只想访问直接子节点时请直接使用 +children+。
359
+ #
360
+ # 参数:
361
+ # * 一个可选块 —— 接收每个被访问的节点。
362
+ #
363
+ # 返回值:给定块时返回 +self+;未给块时返回 +Enumerator+。
364
+ def each(&block)
365
+ return enum_for(:each) unless block
366
+
367
+ yield self
368
+ children.each { |child| child.each(&block) }
369
+ self
80
370
  end
81
- list
82
371
  end
83
372
 
84
- # -- traversal and query -------------------------------------------------
373
+ # 在树上导航与查询:祖先、父子、兄弟、下标。
374
+ module Navigation
375
+ # 本节点所在树的根节点。
376
+ #
377
+ # 根节点的特征是 +parent+ 为 +nil+。若本节点就是根,返回 +self+。
378
+ #
379
+ # 返回值:最顶层的祖先节点。
380
+ def root
381
+ node = self
382
+ node = node.parent while node.parent
383
+ node
384
+ end
85
385
 
86
- # Depth-first (preorder) traversal that yields this node then its
87
- # descendants, depth-first. Returns an Enumerator when no block is given.
88
- def walk(&block)
89
- return enum_for(:walk) unless block
386
+ # 本节点的祖先链,按由近到远排列。
387
+ #
388
+ # 返回值:祖先节点的 +Array+(第一个元素是直接父节点,最后一个元素是根节点);
389
+ # 本节点是根节点时为 +[]+。
390
+ def ancestors
391
+ list = []
392
+ node = @parent
393
+ while node
394
+ list << node
395
+ node = node.parent
396
+ end
397
+ list
398
+ end
90
399
 
91
- block.call(self)
92
- @children.each { |child| child.walk(&block) }
93
- self
94
- end
400
+ # 本节点在「自己父节点」子列表中的位置。
401
+ #
402
+ # 返回值:下标(+Integer+)。
403
+ #
404
+ # 抛出:+RootError+,当本节点是根节点时(根节点没有父节点,给不出位置)。
405
+ def position
406
+ raise RootError, "the root node has no position" unless @parent
95
407
 
96
- # Yield each descendant (including self) whose type matches +type+, or all
97
- # nodes when no type is given. With a block given, also yields when the
98
- # block returns truthy.
99
- def find_all(type = nil)
100
- results = []
101
- walk do |node|
102
- next unless type.nil? || node.type == type
103
- next if block_given? && !yield(node)
408
+ parent_index!
409
+ end
104
410
 
105
- results << node
411
+ # +node+ 在「本节点」子列表中的位置。
412
+ #
413
+ # 参数:
414
+ # * +node+ —— 要查询的子节点(+BaseNode+),不能为 +nil+。
415
+ #
416
+ # 返回值:下标(+Integer+);+node+ 不是本节点的直接子节点时为 +nil+。
417
+ #
418
+ # 抛出:+ArgumentError+,当 +node+ 为 +nil+ 时。
419
+ def index(node)
420
+ raise ArgumentError, "node cannot be nil" if node.nil?
421
+
422
+ children.index(node)
106
423
  end
107
- results
108
- end
109
424
 
110
- # The first matching descendant (or self); +type+ may be a Symbol or a
111
- # callable, or a block may be supplied.
112
- def find(type = nil, &block)
113
- predicate =
114
- if block
115
- block
116
- elsif type.is_a?(Symbol)
117
- ->(n) { n.type == type }
118
- elsif type.respond_to?(:call)
119
- type
120
- else
121
- nil
122
- end
123
- walk { |node| return node if predicate && predicate.call(node) }
124
- nil
125
- end
425
+ # 第一个子节点。
426
+ #
427
+ # 返回值:+BaseNode+;没有子节点时为 +nil+。
428
+ def first_child
429
+ children.first
430
+ end
126
431
 
127
- # Query by one or more types (Symbol or array of Symbols). Returns an array
128
- # of matching nodes (including self if it matches) in document order.
129
- def [](*types)
130
- types = types.flatten
131
- find_all.select { |n| types.include?(n.type) }
132
- end
432
+ # 最后一个子节点。
433
+ #
434
+ # 返回值:+BaseNode+;没有子节点时为 +nil+。
435
+ def last_child
436
+ children.last
437
+ end
133
438
 
134
- # -- mutation -----------------------------------------------------------
439
+ # 本节点的所有兄弟节点(即父节点的全部子节点,「含自身」)。
440
+ #
441
+ # 返回值:节点 +Array+。注意返回的是「副本」,改动它不会影响树;要增删兄弟
442
+ # 关系请使用本类提供的变更方法。
443
+ #
444
+ # 抛出:+RootError+,当本节点是根节点时(根节点没有兄弟)。
445
+ def siblings
446
+ raise RootError, "the root node has no siblings" unless @parent
135
447
 
136
- # Insert +node+ as the sibling immediately before this node.
137
- def insert_before(node)
138
- parent = @parent
139
- raise "cannot insert before a root node" if parent.nil?
448
+ @parent.children.dup
449
+ end
140
450
 
141
- node.remove if node.parent
142
- idx = parent.children.index(self)
143
- parent.children.insert(idx, node)
144
- node.parent = parent
145
- self
146
- end
451
+ # 前一个兄弟节点。
452
+ #
453
+ # 返回值:前一个兄弟节点(+BaseNode+)。
454
+ #
455
+ # 抛出:
456
+ # * +RootError+ —— 本节点是根节点(没有兄弟)。
457
+ # * +BoundaryError+ —— 本节点已经是第一个子节点(没有前一个兄弟)。
458
+ def previous_sibling
459
+ raise RootError, "the root node has no siblings" unless @parent
147
460
 
148
- # Insert +node+ as the sibling immediately after this node.
149
- def insert_after(node)
150
- parent = @parent
151
- raise "cannot insert after a root node" if parent.nil?
461
+ idx = position
462
+ return @parent.children[idx - 1] if idx.positive?
152
463
 
153
- node.remove if node.parent
154
- idx = parent.children.index(self)
155
- parent.children.insert(idx + 1, node)
156
- node.parent = parent
157
- self
158
- end
464
+ raise BoundaryError, "the first child has no previous sibling"
465
+ end
159
466
 
160
- # Replace this node in its parent with +node+. Returns +node+.
161
- def replace(node)
162
- parent = @parent
163
- raise "cannot replace a root node" if parent.nil?
164
-
165
- node.remove if node.parent
166
- idx = parent.children.index(self)
167
- parent.children[idx] = node
168
- node.parent = parent
169
- @parent = nil
170
- node
171
- end
467
+ # 后一个兄弟节点。
468
+ #
469
+ # 返回值:后一个兄弟节点(+BaseNode+)。
470
+ #
471
+ # 抛出:
472
+ # * +RootError+ —— 本节点是根节点(没有兄弟)。
473
+ # * +BoundaryError+ —— 本节点已经是最后一个子节点(没有后一个兄弟)。
474
+ def next_sibling
475
+ raise RootError, "the root node has no siblings" unless @parent
172
476
 
173
- # -- misc ---------------------------------------------------------------
477
+ idx = position
478
+ return @parent.children[idx + 1] if idx < @parent.children.size - 1
174
479
 
175
- def text?
176
- @type == :text
480
+ raise BoundaryError, "the last child has no next sibling"
481
+ end
177
482
  end
178
483
 
179
- # Concatenate the literal string content of this subtree. Leaves that carry
180
- # text expose it via +to_s+ (Text, Code); container nodes recurse.
181
- def plain_text
182
- return "\n" if @type == :softbreak || @type == :hardbreak
183
- return to_s.to_s if @children.empty?
484
+ # 节点状态判定。
485
+ module Predicates
486
+ # 本节点是否是根节点(即没有父节点)。
487
+ #
488
+ # 返回值:+true+ / +false+。
489
+ def root?
490
+ @parent.nil?
491
+ end
184
492
 
185
- @children.map(&:plain_text).join
186
- end
493
+ # 本节点是否是叶子节点(即没有任何子节点)。
494
+ #
495
+ # 返回值:+true+ / +false+,与 #children? 正好相反。
496
+ def leaf?
497
+ children.empty?
498
+ end
187
499
 
188
- # Serialize this node (and its subtree) back to Markdown.
189
- def to_markdown
190
- MDOM::Serializer.serialize(self)
191
- end
192
- alias markdown to_markdown
500
+ # 本节点是否至少拥有一个子节点。
501
+ #
502
+ # 返回值:+true+ / +false+,与 #leaf? 正好相反。
503
+ def children?
504
+ !children.empty?
505
+ end
193
506
 
194
- def empty?
195
- @children.empty?
507
+ # 本节点是否是 +other+ 的祖先(即 +other+ 位于本节点的子树之中)。
508
+ #
509
+ # 参数:
510
+ # * +other+ —— 另一个节点(+BaseNode+),不能为 +nil+。
511
+ #
512
+ # 返回值:+true+ / +false+。本节点自身不算自己的祖先(+false+)。
513
+ #
514
+ # 抛出:+ArgumentError+,当 +other+ 为 +nil+ 时。
515
+ def ancestor_of?(other)
516
+ raise ArgumentError, "other cannot be nil" if other.nil?
517
+
518
+ other.ancestors.include?(self)
519
+ end
196
520
  end
197
521
 
198
- # A compact, single-line summary of this node (its own type/attributes only,
199
- # without recursing into children).
200
- def to_s
201
- "#<#{short_class}#{detail}>"
522
+ # 维护 +depth+ 缓存。
523
+ module Depth
524
+ # 重新计算本节点及其整棵子树的 +depth+。
525
+ #
526
+ # 本节点的 +depth+ 取自父节点(父节点为 +nil+ 时为 +0+),随后递归更新
527
+ # 全部后代。当 +parent+ 与 +children+ 的关系被人为改动、导致 +depth+ 不再
528
+ # 可信时,可用本方法纠正。
529
+ #
530
+ # 返回值:+self+,便于链式调用。
531
+ def sync_depth!
532
+ @depth = @parent ? @parent.depth + 1 : 0
533
+ children.each(&:sync_depth!)
534
+ self
535
+ end
202
536
  end
203
537
 
204
- # A human-readable tree rendering of this node and its descendants.
205
- def inspect
206
- pretty_print
207
- end
538
+ # 同类实例之间协作的内部协议(混入后为 protected)。
539
+ #
540
+ # 这里的方法要么只读地检查自己的状态,要么由*接收者自己*完成状态改写;
541
+ # 没有"父节点伸手去改子节点 +parent+ / +depth+"的做法。外部调用方请使用
542
+ # 上面的公共方法。
543
+ module Internals
544
+ protected
545
+
546
+ # 断言 +node+ 可以作为子节点挂接到本节点下:不为 +nil+,且挂接不会形成环。
547
+ #
548
+ # 只做检查、不改动任何状态;建立关系由 +node.attach_to!+ 完成。
549
+ #
550
+ # 参数:
551
+ # * +node+ —— 待挂接的节点。
552
+ #
553
+ # 抛出:
554
+ # * +ArgumentError+ —— +node+ 为 +nil+。
555
+ # * +CycleError+ —— +node+ 是 +self+ 自身或它的祖先。
556
+ def assert_attachable!(node)
557
+ raise ArgumentError, "node cannot be nil" if node.nil?
558
+
559
+ cyclic = node.equal?(self) || node.ancestor_of?(self)
560
+ raise CycleError, "cannot attach a node to itself or an ancestor" if cyclic
561
+ end
208
562
 
209
- # Render this node plus its subtree as an indented, printable tree.
210
- def pretty_print(level = 0)
211
- indent = " " * level
212
- children = @children.empty? ? "" : "\n" + @children.map { |c| c.pretty_print(level + 1) }.join("\n")
213
- "#{indent}#<#{short_class}#{detail}>#{children}"
214
- end
563
+ # 本节点自己认 +new_parent+ 为父节点:先校验不会形成环,再从原父脱离,
564
+ # 最后接管父子关系并重算本子树的 +depth+。
565
+ #
566
+ # 由 +new_parent+ 的挂载方法调用(+node.attach_to!+),状态改写全部
567
+ # 发生在 +node+ 自己身上。
568
+ #
569
+ # 参数:
570
+ # * +new_parent+ —— 新的父节点(+BaseNode+)。
571
+ #
572
+ # 返回值:+self+。
573
+ #
574
+ # 抛出:+CycleError+ —— +new_parent+ 是本节点自身或本节点的后代。
575
+ def attach_to!(new_parent)
576
+ cyclic = equal?(new_parent) || ancestor_of?(new_parent)
577
+ raise CycleError, "cannot attach a node to itself or an ancestor" if cyclic
578
+
579
+ unlink!
580
+ @parent = new_parent
581
+ sync_depth!
582
+ self
583
+ end
584
+
585
+ # 本节点在父节点子列表中的位置,并断言父节点确实持有本节点。
586
+ #
587
+ # 返回值:下标(+Integer+)。
588
+ #
589
+ # 抛出:+Error+,当父节点的 +children+ 中找不到本节点时(说明 +parent+
590
+ # 与 +children+ 已经不一致)。
591
+ def parent_index!
592
+ idx = @parent.children.index(self)
593
+ raise Error, "the node is not a child of its parent" unless idx
594
+
595
+ idx
596
+ end
215
597
 
216
- private
598
+ # 校验 +index+ 是否落在 +-children.size-1+ 到 +children.size+ 之内,
599
+ # 即 Array#insert 允许的负索引范围。
600
+ #
601
+ # 参数:
602
+ # * +index+ —— 待校验的下标(+Integer+)。
603
+ #
604
+ # 抛出:+IndexError+,当 +index+ 超出该范围时。
605
+ def check_index!(index)
606
+ min = -children.size - 1
607
+ return if index.between?(min, children.size)
608
+
609
+ raise IndexError, "index #{index.inspect} outside of #{min}..#{children.size}"
610
+ end
217
611
 
218
- # The short class name (e.g. "MDOM::Paragraph" -> "Paragraph"), falling back
219
- # to the type symbol for plain nodes.
220
- def short_class
221
- name = self.class.name
222
- name ? name.split("::").last : type.inspect
612
+ # 交换本节点子列表中的两个位置(由子节点在自己的父节点上调用,
613
+ # 即 +swap_children+)。
614
+ #
615
+ # 参数:
616
+ # * +left+ —— 其中一个位置的下标(+Integer+)。
617
+ # * +right+ —— 另一个位置的下标(+Integer+)。
618
+ def swap_children(left, right)
619
+ children[left], children[right] = children[right], children[left]
620
+ end
223
621
  end
224
622
 
225
- # A short description of this node's own salient data, e.g. ' level=2' for a
226
- # heading, ' "text"' for a text node.
227
- def detail
228
- case type
229
- when Types::TEXT, Types::CODE
230
- " #{value.inspect}"
231
- when Types::CODE_BLOCK
232
- lang = attributes[:lang] ? "lang=#{attributes[:lang].inspect} " : ""
233
- " #{lang}literal=#{literal.inspect}"
234
- when Types::HEADING
235
- " level=#{level}"
236
- when Types::LIST
237
- ordered = attributes[:ordered] ? "ordered" : "unordered"
238
- start = attributes[:start] ? ", start=#{attributes[:start]}" : ""
239
- " #{ordered}#{start}"
240
- when Types::LIST_ITEM
241
- task = attributes[:task] ? ", task" : ""
242
- checked = attributes[:checked] ? "=checked" : ""
243
- task.empty? ? "" : " (#{task.sub(', ', '')}#{checked})"
244
- when Types::LINK
245
- title = attributes[:title] ? " title=#{attributes[:title].inspect}" : ""
246
- " #{attributes[:destination].inspect}#{title}"
247
- when Types::IMAGE
248
- title = attributes[:title] ? " title=#{attributes[:title].inspect}" : ""
249
- " #{attributes[:destination].inspect} alt=#{attributes[:alt].inspect}#{title}"
250
- else
251
- attributes.empty? ? "" : " #{attributes.inspect}"
623
+ # 语法树中所有节点的基类。
624
+ #
625
+ # 一个节点由三部分状态构成:
626
+ #
627
+ # * +type+ —— 节点类型标识(+Symbol+,如 +:paragraph+、+:heading+)。
628
+ # * +children+ —— 有序的直接子节点列表(+Array+)。
629
+ # * +attributes+ —— 节点属性表(+Hash+,如标题层级、代码块语言等)。
630
+ #
631
+ # 另外两项状态由树结构派生而来:
632
+ #
633
+ # * +parent+ —— 父节点;根节点的 +parent+ 为 +nil+。
634
+ # * +depth+ —— 节点在树中的深度;根节点为 +0+,逐层加一。
635
+ #
636
+ # === 能力划分
637
+ #
638
+ # 本类的行为来自若干 mixin(见 +MDOM::Node+ 的说明),自身只保留状态、初始化
639
+ # 与 +Enumerable+ 契约。若要扩展节点能力,优先新增模块再混入,而不是把
640
+ # 方法继续堆进这个类。
641
+ #
642
+ # === 结构不变式
643
+ #
644
+ # 下面所有变更方法共同维护以下不变式:
645
+ #
646
+ # * 一个子节点在同一时刻只属于一个父节点;
647
+ # * +parent+ 与 +children+ 双向一致(互为引用,不会出现单边挂接);
648
+ # * 子树的 +depth+ 始终与它在整棵树中的实际位置一致。
649
+ #
650
+ # === 返回值约定
651
+ #
652
+ # 除以下两种情形外,所有变更方法都返回 +self+,以便链式调用:
653
+ #
654
+ # * #extract_at 返回被摘除的子节点(越界时为 +nil+);
655
+ # * #replace_with 与 #wrap_with 返回替换或包装后的新节点。
656
+ #
657
+ # === 底层写入器提示
658
+ #
659
+ # #parent= 是低层写入器:直接赋值既不会同步父节点的 +children+,也不会
660
+ # 更新 +depth+。除非你确切知道自己在做什么,否则请使用上面的变更方法,
661
+ # 而不是直接给它赋值。
662
+ class BaseNode
663
+ include Enumerable
664
+ include Attaching
665
+ include Detaching
666
+ include Replacing
667
+ include Reordering
668
+ include Traversal
669
+ include Navigation
670
+ include Predicates
671
+ include Depth
672
+ include Internals
673
+
674
+ # 父节点;根节点为 +nil+。作为底层写入器使用,详见类说明。
675
+ attr_accessor :parent
676
+
677
+ # 节点属性表(+Hash+)。
678
+ attr_accessor :attributes
679
+
680
+ # 节点类型标识(+Symbol+)。
681
+ attr_reader :type
682
+
683
+ # 节点在树中的深度,根节点为 +0+。
684
+ attr_reader :depth
685
+
686
+ # 该节点是否含有内容(+true+ / +false+),具体含义由子类决定。
687
+ attr_reader :has_content
688
+
689
+ # 直接子节点列表(+Array+)。如需变更,请使用本类提供的变更方法。
690
+ attr_reader :children
691
+
692
+ # 在 CSM §5.3 的 +point+ 上注册一个钩子(委托 +MDOM::Hooks+)。类级——对所有
693
+ # 文档生效;只想影响一次调用时用 +MDOM.compile(source, hooks: ...)+。
694
+ #
695
+ # MDOM::Node::Document.register_hook :before_compile do |doc|
696
+ # doc.meta[:generated_at] = Time.now.iso8601
697
+ # end
698
+ def self.register_hook(point, &block)
699
+ MDOM::Hooks.register(point, &block)
700
+ end
701
+
702
+ # 取消本类注册过的一个钩子。
703
+ def self.unregister_hook(point, block)
704
+ MDOM::Hooks.unregister(point, block)
705
+ end
706
+
707
+ # 创建一个新节点。
708
+ #
709
+ # +children+ 中的每个元素都会经 #<< 逐个挂接,因此从初始化起就享有
710
+ # 变更方法的全部不变量保障(先脱离原父节点、校验、同步 +depth+)。
711
+ #
712
+ # 参数:
713
+ # * +type+ —— 节点类型标识(+Symbol+)。
714
+ # * +attributes+ —— 节点属性表(+Hash+),默认为空。
715
+ # * +children+ —— 初始子节点(+Array+),默认为空。
716
+ #
717
+ # 返回值:新建的节点(+self+)。
718
+ #
719
+ # 抛出:
720
+ # * +ArgumentError+ —— 某个初始子节点为 +nil+。
721
+ # * +CycleError+ —— 某个初始子节点就是本节点自身或其祖先。
722
+ def initialize(type, attributes: {}, children: [])
723
+ @type = type
724
+ @attributes = attributes
725
+ @children = []
726
+ @parent = nil
727
+ @depth = 0
728
+ @has_content = false
729
+ children.each { |child| self << child }
252
730
  end
253
731
  end
254
732
  end