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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +71 -0
- data/LICENSE.txt +21 -0
- data/README.md +285 -0
- data/USAGE.md +789 -0
- data/lib/mdom/block_detector.rb +200 -0
- data/lib/mdom/builder.rb +378 -163
- data/lib/mdom/error.rb +22 -0
- data/lib/mdom/hooks.rb +81 -0
- data/lib/mdom/html_serializer.rb +330 -0
- data/lib/mdom/inline_parser.rb +541 -142
- data/lib/mdom/markdown_serializer.rb +332 -0
- data/lib/mdom/node/inline_nodes.rb +239 -0
- data/lib/mdom/node/nodes.rb +336 -0
- data/lib/mdom/node.rb +677 -199
- data/lib/mdom/parser.rb +257 -235
- data/lib/mdom/tokenizer.rb +415 -0
- data/lib/mdom/translator.rb +379 -0
- data/lib/mdom/version.rb +1 -1
- data/lib/mdom.rb +58 -7
- metadata +40 -7
- data/lib/mdom/nodes.rb +0 -214
- data/lib/mdom/serializer.rb +0 -165
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
|
-
#
|
|
7
|
+
# Markdown 语法树的节点定义。
|
|
5
8
|
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
42
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
#
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
400
|
+
# 本节点在「自己父节点」子列表中的位置。
|
|
401
|
+
#
|
|
402
|
+
# 返回值:下标(+Integer+)。
|
|
403
|
+
#
|
|
404
|
+
# 抛出:+RootError+,当本节点是根节点时(根节点没有父节点,给不出位置)。
|
|
405
|
+
def position
|
|
406
|
+
raise RootError, "the root node has no position" unless @parent
|
|
95
407
|
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
432
|
+
# 最后一个子节点。
|
|
433
|
+
#
|
|
434
|
+
# 返回值:+BaseNode+;没有子节点时为 +nil+。
|
|
435
|
+
def last_child
|
|
436
|
+
children.last
|
|
437
|
+
end
|
|
133
438
|
|
|
134
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
parent = @parent
|
|
139
|
-
raise "cannot insert before a root node" if parent.nil?
|
|
448
|
+
@parent.children.dup
|
|
449
|
+
end
|
|
140
450
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
149
|
-
|
|
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
|
-
|
|
154
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
477
|
+
idx = position
|
|
478
|
+
return @parent.children[idx + 1] if idx < @parent.children.size - 1
|
|
174
479
|
|
|
175
|
-
|
|
176
|
-
|
|
480
|
+
raise BoundaryError, "the last child has no next sibling"
|
|
481
|
+
end
|
|
177
482
|
end
|
|
178
483
|
|
|
179
|
-
#
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
484
|
+
# 节点状态判定。
|
|
485
|
+
module Predicates
|
|
486
|
+
# 本节点是否是根节点(即没有父节点)。
|
|
487
|
+
#
|
|
488
|
+
# 返回值:+true+ / +false+。
|
|
489
|
+
def root?
|
|
490
|
+
@parent.nil?
|
|
491
|
+
end
|
|
184
492
|
|
|
185
|
-
|
|
186
|
-
|
|
493
|
+
# 本节点是否是叶子节点(即没有任何子节点)。
|
|
494
|
+
#
|
|
495
|
+
# 返回值:+true+ / +false+,与 #children? 正好相反。
|
|
496
|
+
def leaf?
|
|
497
|
+
children.empty?
|
|
498
|
+
end
|
|
187
499
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
500
|
+
# 本节点是否至少拥有一个子节点。
|
|
501
|
+
#
|
|
502
|
+
# 返回值:+true+ / +false+,与 #leaf? 正好相反。
|
|
503
|
+
def children?
|
|
504
|
+
!children.empty?
|
|
505
|
+
end
|
|
193
506
|
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
#
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
#
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|