mdom 0.1.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 +486 -0
- 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 +59 -7
- metadata +41 -7
- data/lib/mdom/nodes.rb +0 -214
- data/lib/mdom/serializer.rb +0 -165
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../node"
|
|
4
|
+
|
|
5
|
+
# Markdown DOM 的 AST1 节点定义。
|
|
6
|
+
#
|
|
7
|
+
# 块级解析产出的树只描述**块结构**:段落、标题、列表等的正文一律以
|
|
8
|
+
# +attributes[:text]+ 保存**尚未做行内解析**的原文,等待后续的 InlineParser
|
|
9
|
+
# 展开为行内节点。嵌入节点(RubyEmbed)保存嵌入源码原文与位置,不求值。
|
|
10
|
+
#
|
|
11
|
+
# 每个具体节点类只固定自己的 +type+ 与语义属性,其余能力(挂载、遍历、
|
|
12
|
+
# 导航、深度维护……)全部继承自 +BaseNode+。
|
|
13
|
+
module MDOM
|
|
14
|
+
module Node
|
|
15
|
+
# 文档根节点。
|
|
16
|
+
class Document < BaseNode
|
|
17
|
+
def initialize(attributes: {}, children: [])
|
|
18
|
+
super(:document, attributes: attributes, children: children)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# 文档级元数据(+Hash+,惰性创建)。钩子常往里放东西(CSM §5.3),
|
|
22
|
+
# +MDOM.compile+ 也会把它作为 +:after_serialize+ 的第二个参数交出去。
|
|
23
|
+
def meta
|
|
24
|
+
attributes[:meta] ||= {}
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# 节块:连续四个 `\n` 开启的新节,映射为 `<div>`。
|
|
29
|
+
class Div < BaseNode
|
|
30
|
+
def initialize(attributes: {}, children: [])
|
|
31
|
+
super(:div, attributes: attributes, children: children)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# 段落。+text+ 是尚未做行内解析的原文。
|
|
36
|
+
class Paragraph < BaseNode
|
|
37
|
+
def initialize(text = "", attributes: {}, children: [])
|
|
38
|
+
super(:paragraph, attributes: { text: text }.merge(attributes), children: children)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def text
|
|
42
|
+
attributes[:text]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def text=(value)
|
|
46
|
+
attributes[:text] = value
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# ATX 标题。+level+ 为 1–6。
|
|
51
|
+
class Heading < BaseNode
|
|
52
|
+
def initialize(level, text = "", closed: false, attributes: {}, children: [])
|
|
53
|
+
attrs = { level: level, text: text, closed: closed }.merge(attributes)
|
|
54
|
+
super(:heading, attributes: attrs, children: children)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def level
|
|
58
|
+
attributes[:level]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def text
|
|
62
|
+
attributes[:text]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# 是否以**等长**的 `#` 收尾(CSM §1.2 的 hgroup 用法)。
|
|
66
|
+
def closed?
|
|
67
|
+
attributes[:closed]
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# 内容策略为 FLOW 的容器(CSM 元素表:`blockquote` / `li` / `dd`)。
|
|
72
|
+
#
|
|
73
|
+
# 它们的正文**不直接挂在容器上**,而是先成为一个段落——FLOW 容器的直接子节点
|
|
74
|
+
# 是块级节点。+text+ 因此是派生读取:取第一个段落子节点的原文。
|
|
75
|
+
module FlowBody
|
|
76
|
+
# 正文原文;还没有段落时返回 +nil+。
|
|
77
|
+
def text
|
|
78
|
+
paragraph = children.find { |child| child.type == :paragraph }
|
|
79
|
+
paragraph&.text
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# 块引用。正文(FLOW 内容)在 +children+ 里,通常是段落。
|
|
84
|
+
class Blockquote < BaseNode
|
|
85
|
+
include FlowBody
|
|
86
|
+
|
|
87
|
+
def initialize(attributes: {}, children: [])
|
|
88
|
+
super(:blockquote, attributes: attributes, children: children)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# `>-` 的出处:派生自 #Cite 子节点(CSM §1.4),没有时 +nil+。
|
|
92
|
+
def cite
|
|
93
|
+
source = children.find { |child| child.type == :cite }
|
|
94
|
+
source&.text
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# 引用来源(`<cite>`,由 `>-` 收尾产生)。内容策略为 PHRASING(CSM §1.4),
|
|
99
|
+
# 所以正文直接是行内节点。
|
|
100
|
+
class Cite < BaseNode
|
|
101
|
+
def initialize(text = "", attributes: {}, children: [])
|
|
102
|
+
super(:cite, attributes: { text: text }.merge(attributes), children: children)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def text
|
|
106
|
+
attributes[:text]
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def text=(value)
|
|
110
|
+
attributes[:text] = value
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# 围栏代码块。+code+ 是围栏内的原文(不做任何转义)。
|
|
115
|
+
class CodeBlock < BaseNode
|
|
116
|
+
def initialize(code = "", lang: nil, params: nil, fence: 3, attributes: {}, children: [])
|
|
117
|
+
attrs = { code: code, lang: lang, params: params, fence: fence }.merge(attributes)
|
|
118
|
+
super(:code_block, attributes: attrs, children: children)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def code
|
|
122
|
+
attributes[:code]
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def lang
|
|
126
|
+
attributes[:lang]
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def params
|
|
130
|
+
attributes[:params]
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# 围栏的反引号个数(CSM §1.5:3–6 个)。
|
|
134
|
+
def fence
|
|
135
|
+
attributes[:fence]
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# 列表容器。+ordered+ 为 +true+ 时是有序列表;+task+ 为 +true+ 时是任务列表。
|
|
140
|
+
class List < BaseNode
|
|
141
|
+
def initialize(ordered: false, task: false, marker: "-", attributes: {}, children: [])
|
|
142
|
+
attrs = { ordered: ordered, task: task, marker: marker }.merge(attributes)
|
|
143
|
+
super(:list, attributes: attrs, children: children)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def ordered?
|
|
147
|
+
attributes[:ordered]
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def task?
|
|
151
|
+
attributes[:task]
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# 列表项。正文(FLOW 内容)在 +children+ 里,通常是段落。
|
|
156
|
+
# +checked+ 仅在任务列表中非 +nil+(+true+ / +false+)。
|
|
157
|
+
class ListItem < BaseNode
|
|
158
|
+
include FlowBody
|
|
159
|
+
|
|
160
|
+
def initialize(marker: "-", task: false, checked: nil, attributes: {}, children: [])
|
|
161
|
+
attrs = { marker: marker, task: task, checked: checked }.merge(attributes)
|
|
162
|
+
super(:list_item, attributes: attrs, children: children)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def marker
|
|
166
|
+
attributes[:marker]
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def task?
|
|
170
|
+
attributes[:task]
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def checked
|
|
174
|
+
attributes[:checked]
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# 定义列表容器。
|
|
179
|
+
class DefinitionList < BaseNode
|
|
180
|
+
def initialize(attributes: {}, children: [])
|
|
181
|
+
super(:definition_list, attributes: attributes, children: children)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# 定义术语(`<dt>`)。
|
|
186
|
+
class Term < BaseNode
|
|
187
|
+
def initialize(text = "", attributes: {}, children: [])
|
|
188
|
+
super(:term, attributes: { text: text }.merge(attributes), children: children)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def text
|
|
192
|
+
attributes[:text]
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# 定义内容(`<dd>`)。正文(FLOW 内容)在 +children+ 里,通常是段落。
|
|
197
|
+
class Definition < BaseNode
|
|
198
|
+
include FlowBody
|
|
199
|
+
|
|
200
|
+
def initialize(attributes: {}, children: [])
|
|
201
|
+
super(:definition, attributes: attributes, children: children)
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# 表格(FLOW,**纯结构**):行是 +TableRow+,行内是 +TableHeaderCell+ /
|
|
206
|
+
# +TableCell+。单元格内容策略为 PHRASING(CSM §1.12)。
|
|
207
|
+
class Table < BaseNode
|
|
208
|
+
def initialize(attributes: {}, children: [])
|
|
209
|
+
super(:table, attributes: attributes, children: children)
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# 表格行(`<tr>`,纯结构)。
|
|
214
|
+
class TableRow < BaseNode
|
|
215
|
+
def initialize(attributes: {}, children: [])
|
|
216
|
+
super(:table_row, attributes: attributes, children: children)
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# `th` / `td` 共用:正文(PHRASING 原文)与列对齐。
|
|
221
|
+
# +align+ 为 +nil+(默认对齐)/ +:left+ / +:center+ / +:right+(CSM §1.12)。
|
|
222
|
+
module Cell
|
|
223
|
+
def text
|
|
224
|
+
attributes[:text]
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def text=(value)
|
|
228
|
+
attributes[:text] = value
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def align
|
|
232
|
+
attributes[:align]
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def align=(value)
|
|
236
|
+
attributes[:align] = value
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# 表头单元格(`th`)。由分隔行把它前面那一行提升而来。
|
|
241
|
+
class TableHeaderCell < BaseNode
|
|
242
|
+
include Cell
|
|
243
|
+
|
|
244
|
+
def initialize(text = "", align: nil, attributes: {}, children: [])
|
|
245
|
+
attrs = { text: text, align: align }.merge(attributes)
|
|
246
|
+
super(:table_header_cell, attributes: attrs, children: children)
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# 数据单元格(`td`)。
|
|
251
|
+
class TableCell < BaseNode
|
|
252
|
+
include Cell
|
|
253
|
+
|
|
254
|
+
def initialize(text = "", align: nil, attributes: {}, children: [])
|
|
255
|
+
attrs = { text: text, align: align }.merge(attributes)
|
|
256
|
+
super(:table_cell, attributes: attrs, children: children)
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# 分隔线(`<hr>`)。
|
|
261
|
+
class Hrule < BaseNode
|
|
262
|
+
def initialize(attributes: {}, children: [])
|
|
263
|
+
super(:hrule, attributes: attributes, children: children)
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# 块级公式(`$$…$$`,FLOW)。+tex+ 是 TeX 原文(由 KaTeX 渲染,CSM §1.11)。
|
|
268
|
+
class MathBlock < BaseNode
|
|
269
|
+
def initialize(tex = "", attributes: {}, children: [])
|
|
270
|
+
super(:math_block, attributes: { tex: tex }.merge(attributes), children: children)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def tex
|
|
274
|
+
attributes[:tex]
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Ruby 嵌入块(三类通道统一为此节点)。
|
|
279
|
+
#
|
|
280
|
+
# * +source+ —— 已剥离块首行 CSM 缩进后的源码原文;
|
|
281
|
+
# * +form+ —— +:begin+(引导块关键字)/ +:free+(方法调用带块)/ +:tilde+(`~` 行);
|
|
282
|
+
# * +lines+ —— 在**文档**中的行号区间(+Range+,1 起);
|
|
283
|
+
# * +indent+ —— 块首行的 CSM 缩进(空格数);
|
|
284
|
+
# * +render+ —— 是否以 `.render` 收尾(`~` 行恒为 +false+)。
|
|
285
|
+
#
|
|
286
|
+
# 节点只承载「已剥离 N 层缩进」的字符串;块在 CSM 树中的层级由节点在树中的
|
|
287
|
+
# 位置决定,不由字符串承载。
|
|
288
|
+
class RubyEmbed < BaseNode
|
|
289
|
+
def initialize(source:, form:, lines:, indent: 0, render: false, attributes: {}, children: [])
|
|
290
|
+
attrs = {
|
|
291
|
+
source: source,
|
|
292
|
+
form: form,
|
|
293
|
+
lines: lines,
|
|
294
|
+
indent: indent,
|
|
295
|
+
render: render
|
|
296
|
+
}.merge(attributes)
|
|
297
|
+
super(:ruby_embed, attributes: attrs, children: children)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def source
|
|
301
|
+
attributes[:source]
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def form
|
|
305
|
+
attributes[:form]
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def lines
|
|
309
|
+
attributes[:lines]
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def indent
|
|
313
|
+
attributes[:indent]
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def render?
|
|
317
|
+
attributes[:render]
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# `__END__` 边界。其后的原文挂在 +data+ 上,直通给 Ruby 的 +DATA+。
|
|
322
|
+
class EndMarker < BaseNode
|
|
323
|
+
def initialize(line, data = nil, attributes: {}, children: [])
|
|
324
|
+
super(:end_marker, attributes: { line: line, data: data }.merge(attributes), children: children)
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def line
|
|
328
|
+
attributes[:line]
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def data
|
|
332
|
+
attributes[:data]
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
end
|