paru 1.5.0 → 1.5.1
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/lib/paru/error.rb +6 -4
- data/lib/paru/filter/ast_manipulation.rb +90 -91
- data/lib/paru/filter/attr.rb +75 -69
- data/lib/paru/filter/block.rb +15 -14
- data/lib/paru/filter/block_quote.rb +14 -12
- data/lib/paru/filter/bullet_list.rb +17 -16
- data/lib/paru/filter/caption.rb +50 -48
- data/lib/paru/filter/cell.rb +52 -50
- data/lib/paru/filter/citation.rb +53 -51
- data/lib/paru/filter/cite.rb +34 -33
- data/lib/paru/filter/code.rb +51 -49
- data/lib/paru/filter/code_block.rb +76 -76
- data/lib/paru/filter/col_spec.rb +58 -56
- data/lib/paru/filter/definition_list.rb +51 -52
- data/lib/paru/filter/definition_list_item.rb +45 -43
- data/lib/paru/filter/div.rb +37 -35
- data/lib/paru/filter/document.rb +112 -115
- data/lib/paru/filter/emph.rb +7 -5
- data/lib/paru/filter/empty_block.rb +17 -16
- data/lib/paru/filter/empty_inline.rb +23 -22
- data/lib/paru/filter/figure.rb +41 -39
- data/lib/paru/filter/header.rb +41 -39
- data/lib/paru/filter/horizontal_rule.rb +7 -5
- data/lib/paru/filter/image.rb +13 -12
- data/lib/paru/filter/inline.rb +27 -26
- data/lib/paru/filter/inner_markdown.rb +60 -62
- data/lib/paru/filter/int_value.rb +19 -18
- data/lib/paru/filter/line_block.rb +13 -11
- data/lib/paru/filter/line_break.rb +7 -5
- data/lib/paru/filter/link.rb +34 -33
- data/lib/paru/filter/list.rb +37 -37
- data/lib/paru/filter/list_attributes.rb +52 -51
- data/lib/paru/filter/math.rb +66 -64
- data/lib/paru/filter/meta.rb +40 -39
- data/lib/paru/filter/meta_blocks.rb +7 -5
- data/lib/paru/filter/meta_bool.rb +7 -5
- data/lib/paru/filter/meta_inlines.rb +9 -7
- data/lib/paru/filter/meta_list.rb +7 -5
- data/lib/paru/filter/meta_map.rb +50 -49
- data/lib/paru/filter/meta_string.rb +7 -6
- data/lib/paru/filter/meta_value.rb +26 -25
- data/lib/paru/filter/metadata.rb +150 -88
- data/lib/paru/filter/node.rb +400 -406
- data/lib/paru/filter/note.rb +29 -29
- data/lib/paru/filter/null.rb +7 -5
- data/lib/paru/filter/ordered_list.rb +50 -49
- data/lib/paru/filter/para.rb +21 -20
- data/lib/paru/filter/plain.rb +23 -21
- data/lib/paru/filter/quoted.rb +28 -26
- data/lib/paru/filter/short_caption.rb +7 -5
- data/lib/paru/filter/small_caps.rb +8 -7
- data/lib/paru/filter/soft_break.rb +7 -5
- data/lib/paru/filter/space.rb +7 -5
- data/lib/paru/filter/span.rb +29 -27
- data/lib/paru/filter/str.rb +33 -32
- data/lib/paru/filter/strikeout.rb +7 -6
- data/lib/paru/filter/strong.rb +7 -6
- data/lib/paru/filter/subscript.rb +7 -6
- data/lib/paru/filter/superscript.rb +7 -6
- data/lib/paru/filter/table.rb +201 -210
- data/lib/paru/filter/table_body.rb +67 -67
- data/lib/paru/filter/table_end.rb +53 -55
- data/lib/paru/filter/table_foot.rb +8 -7
- data/lib/paru/filter/table_head.rb +8 -7
- data/lib/paru/filter/target.rb +29 -27
- data/lib/paru/filter/underline.rb +7 -5
- data/lib/paru/filter/value.rb +74 -75
- data/lib/paru/filter/version.rb +23 -22
- data/lib/paru/filter.rb +355 -331
- data/lib/paru/filter_error.rb +7 -5
- data/lib/paru/info.rb +29 -30
- data/lib/paru/pandoc.rb +241 -248
- data/lib/paru/pandoc2yaml.rb +51 -42
- data/lib/paru/selector.rb +193 -184
- data/lib/paru.rb +3 -1
- metadata +4 -73
data/lib/paru/filter/node.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
#--
|
2
4
|
# Copyright 2015, 2016, 2017, 2020, 2023 Huub de Beer <Huub@heerdebeer.org>
|
3
5
|
#
|
@@ -16,418 +18,410 @@
|
|
16
18
|
# You should have received a copy of the GNU General Public License
|
17
19
|
# along with Paru. If not, see <http://www.gnu.org/licenses/>.
|
18
20
|
#++
|
19
|
-
require_relative
|
20
|
-
require_relative '
|
21
|
+
require_relative '../pandoc'
|
22
|
+
require_relative 'ast_manipulation'
|
21
23
|
|
22
24
|
module Paru
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
# A Paru::Pandoc converter from markdown to JSON
|
33
|
-
MARKDOWN2JSON = Paru::Pandoc.new do
|
34
|
-
from "markdown+smart"
|
35
|
-
to "json"
|
36
|
-
preserve_tabs true
|
37
|
-
end
|
38
|
-
|
39
|
-
# Every node in a Pandoc AST is mapped to Node. Filters are all about
|
40
|
-
# manipulating Nodes.
|
41
|
-
#
|
42
|
-
# @!attribute parent
|
43
|
-
# @return [Node] the parent node, if any.
|
44
|
-
class Node
|
45
|
-
include Enumerable
|
46
|
-
include ASTManipulation
|
47
|
-
|
48
|
-
attr_accessor :parent
|
49
|
-
|
50
|
-
# Block level nodes
|
51
|
-
require_relative './block_quote.rb'
|
52
|
-
require_relative './block.rb'
|
53
|
-
require_relative './bullet_list.rb'
|
54
|
-
require_relative './code_block.rb'
|
55
|
-
require_relative './definition_list_item.rb'
|
56
|
-
require_relative './definition_list.rb'
|
57
|
-
require_relative './div.rb'
|
58
|
-
require_relative './empty_block.rb'
|
59
|
-
require_relative './header.rb'
|
60
|
-
require_relative './horizontal_rule.rb'
|
61
|
-
require_relative './line_block.rb'
|
62
|
-
require_relative './null.rb'
|
63
|
-
require_relative './ordered_list.rb'
|
64
|
-
require_relative './para.rb'
|
65
|
-
require_relative './plain.rb'
|
66
|
-
require_relative './raw_block.rb'
|
67
|
-
require_relative './table.rb'
|
68
|
-
require_relative './caption.rb'
|
69
|
-
require_relative './table_head.rb'
|
70
|
-
require_relative './table_foot.rb'
|
71
|
-
require_relative './row.rb'
|
72
|
-
require_relative './cell.rb'
|
73
|
-
require_relative './figure.rb'
|
74
|
-
|
75
|
-
# Inline level nodes
|
76
|
-
require_relative './cite.rb'
|
77
|
-
require_relative './code.rb'
|
78
|
-
require_relative './emph.rb'
|
79
|
-
require_relative './empty_inline.rb'
|
80
|
-
require_relative './image.rb'
|
81
|
-
require_relative './inline.rb'
|
82
|
-
require_relative './line_break.rb'
|
83
|
-
require_relative './link.rb'
|
84
|
-
require_relative './math.rb'
|
85
|
-
require_relative './note.rb'
|
86
|
-
require_relative './quoted.rb'
|
87
|
-
require_relative './raw_inline.rb'
|
88
|
-
require_relative './small_caps.rb'
|
89
|
-
require_relative './soft_break.rb'
|
90
|
-
require_relative './space.rb'
|
91
|
-
require_relative './span.rb'
|
92
|
-
require_relative './strikeout.rb'
|
93
|
-
require_relative './strong.rb'
|
94
|
-
require_relative './str.rb'
|
95
|
-
require_relative './subscript.rb'
|
96
|
-
require_relative './superscript.rb'
|
97
|
-
require_relative './short_caption.rb'
|
98
|
-
require_relative './underline.rb'
|
99
|
-
|
100
|
-
# Metadata level nodes
|
101
|
-
require_relative './meta_blocks.rb'
|
102
|
-
require_relative './meta_bool.rb'
|
103
|
-
require_relative './meta_inlines.rb'
|
104
|
-
require_relative './meta_list.rb'
|
105
|
-
require_relative './meta_map.rb'
|
106
|
-
require_relative './meta_string.rb'
|
107
|
-
|
108
|
-
# Create a new Node with contents. Also indicate if this node has
|
109
|
-
# inline children or block children.
|
110
|
-
#
|
111
|
-
# @param contents [Array<pandoc node in JSON> = []] the contents of
|
112
|
-
# this node
|
113
|
-
# @param inline_children [Boolean] does this node have
|
114
|
-
# inline children (true) or block children (false).
|
115
|
-
def initialize(contents = [], inline_children = false)
|
116
|
-
@children = []
|
117
|
-
@parent = nil
|
118
|
-
|
119
|
-
if contents.is_a? Array
|
120
|
-
contents.each do |elt|
|
121
|
-
if PandocFilter.const_defined? elt["t"]
|
122
|
-
child = PandocFilter.const_get(elt["t"]).new elt["c"]
|
123
|
-
else
|
124
|
-
if inline_children
|
125
|
-
child = PandocFilter::Inline.new elt["c"]
|
126
|
-
else
|
127
|
-
child = PandocFilter::Plain.new elt["c"]
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
child.parent = self
|
132
|
-
@children.push child
|
133
|
-
end
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
# Create a new node from a markdown string. This is always a block
|
138
|
-
# level node. If more
|
139
|
-
# than one new node is created, a {Div} is created as a parent for
|
140
|
-
# the newly created block nodes..
|
141
|
-
#
|
142
|
-
# @param markdown_string [String] the markdown string to convert
|
143
|
-
# to a AST node
|
144
|
-
#
|
145
|
-
# @return [Block|Div] The {Block} node created by converting
|
146
|
-
# markdown_string with pandoc; A {Div} node if this conversion
|
147
|
-
# holds more than one {Block} node.
|
148
|
-
def self.from_markdown(markdown_string)
|
149
|
-
node = Node.new []
|
150
|
-
node.outer_markdown = markdown_string
|
151
|
-
|
152
|
-
if node.children.size == 1
|
153
|
-
node = node.children.first
|
154
|
-
else
|
155
|
-
container = from_markdown "<div></div>"
|
156
|
-
container.children = node.children
|
157
|
-
node = container
|
158
|
-
end
|
159
|
-
|
160
|
-
return node
|
161
|
-
end
|
162
|
-
|
163
|
-
# For each child of this Node, yield the child
|
164
|
-
#
|
165
|
-
# @yield [Node]
|
166
|
-
def each()
|
167
|
-
@children.each do |child|
|
168
|
-
yield child
|
169
|
-
end
|
170
|
-
end
|
171
|
-
|
172
|
-
# Does this node have any children?
|
173
|
-
#
|
174
|
-
# @return [Boolean] True if this node has any children, false
|
175
|
-
# otherwise.
|
176
|
-
def has_children?()
|
177
|
-
defined? @children and not @children.nil? and @children.size > 0
|
178
|
-
end
|
179
|
-
|
180
|
-
# Get this node's children,
|
181
|
-
#
|
182
|
-
# @return [Array<Node>] this node's children as an Array.
|
183
|
-
def children()
|
184
|
-
if has_children?
|
185
|
-
@children
|
186
|
-
else
|
187
|
-
[]
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
# Set this node's children
|
192
|
-
#
|
193
|
-
# @param list [Array<Node>] a list with nodes
|
194
|
-
def children=(list)
|
195
|
-
@children = list
|
196
|
-
end
|
197
|
-
|
198
|
-
# Does this node have a parent?
|
199
|
-
#
|
200
|
-
# @return [Boolean] True if this node has a parent, false
|
201
|
-
# otherwise.
|
202
|
-
def has_parent?()
|
203
|
-
not @parent.nil?
|
204
|
-
end
|
205
|
-
|
206
|
-
# Is this a root node?
|
207
|
-
#
|
208
|
-
# @return [Boolean] True if this node has a no parent, false
|
209
|
-
# otherwise
|
210
|
-
def is_root?()
|
211
|
-
not has_parent?
|
212
|
-
end
|
213
|
-
|
214
|
-
# Is this Node a Node or a leaf? See #is_leaf?
|
215
|
-
#
|
216
|
-
# @return [Boolean] A node is a node if it is not a leaf.
|
217
|
-
def is_node?()
|
218
|
-
not is_leaf
|
219
|
-
end
|
220
|
-
|
221
|
-
# Is this Node a leaf? See also #is_node?
|
222
|
-
#
|
223
|
-
# @return [Boolean] A node is a leaf when it has no children
|
224
|
-
# false otherwise
|
225
|
-
def is_leaf?()
|
226
|
-
not has_children?
|
227
|
-
end
|
228
|
-
|
229
|
-
# Does this node has a string value?
|
230
|
-
#
|
231
|
-
# @return [Boolean] true if this node has a string value, false
|
232
|
-
# otherwise
|
233
|
-
def has_string?()
|
234
|
-
false
|
235
|
-
end
|
236
|
-
|
237
|
-
# Does this node have Inline contents?
|
238
|
-
#
|
239
|
-
# @return [Boolean] true if this node has Inline contents, false
|
240
|
-
# otherwise
|
241
|
-
def has_inline?()
|
242
|
-
false
|
243
|
-
end
|
244
|
-
|
245
|
-
# Does this node have Block contents?
|
246
|
-
#
|
247
|
-
# @return [Boolean] true if this node has Block contents, false
|
248
|
-
# otherwise
|
249
|
-
def has_block?()
|
250
|
-
false
|
251
|
-
end
|
252
|
-
|
253
|
-
# Is this node a Block level node?
|
254
|
-
#
|
255
|
-
# @return [Boolean] true if this node is a block level node, false
|
256
|
-
# otherwise
|
257
|
-
def is_block?()
|
258
|
-
false
|
259
|
-
end
|
260
|
-
|
261
|
-
# Can this node act both as a block and inline node? Some nodes
|
262
|
-
# are hybrids in this regard, like Math or Image
|
263
|
-
#
|
264
|
-
# @return [Boolean]
|
265
|
-
def can_act_as_both_block_and_inline?()
|
266
|
-
false
|
267
|
-
end
|
268
|
-
|
269
|
-
# Is this an Inline level node?
|
270
|
-
#
|
271
|
-
# @return [Boolean] true if this node is an inline level node,
|
272
|
-
# false otherwise
|
273
|
-
def is_inline?()
|
274
|
-
false
|
275
|
-
end
|
276
|
-
|
277
|
-
# Convert this Node to a metadata value. If this Node
|
278
|
-
# {is_inline?}, it is converted to {MetaInlines} if it is
|
279
|
-
# {is_block?}, it is converted to {MetaBlocks}.
|
280
|
-
#
|
281
|
-
# @return [MetaInlines|MetaBlocks]
|
282
|
-
def toMetadata()
|
283
|
-
if is_inline? then
|
284
|
-
MetaInlines.new to_ast, true
|
285
|
-
elsif is_blocks? then
|
286
|
-
MetaBlocks.new to_ast, false
|
287
|
-
else
|
288
|
-
# ?
|
289
|
-
end
|
290
|
-
end
|
291
|
-
|
292
|
-
# If this node has attributes with classes, is name among them?
|
293
|
-
#
|
294
|
-
# @param name [String] the class name to search for
|
295
|
-
#
|
296
|
-
# @return [Boolean] true if this node has attributes with classes
|
297
|
-
# and name is among them, false otherwise
|
298
|
-
def has_class?(name)
|
299
|
-
if not @attr.nil?
|
300
|
-
@attr.has_class? name
|
301
|
-
else
|
302
|
-
false
|
303
|
-
end
|
304
|
-
end
|
305
|
-
|
306
|
-
# A String representation of this Node
|
307
|
-
#
|
308
|
-
# @return [String]
|
309
|
-
def to_s()
|
310
|
-
self.class.name
|
311
|
-
end
|
312
|
-
|
313
|
-
# The pandoc type of this Node
|
314
|
-
#
|
315
|
-
# @return [String]
|
316
|
-
def type()
|
317
|
-
ast_type
|
318
|
-
end
|
319
|
-
|
320
|
-
# The AST type of this Node
|
321
|
-
#
|
322
|
-
# @return [String]
|
323
|
-
def ast_type()
|
324
|
-
self.class.name.split("::").last
|
325
|
-
end
|
326
|
-
|
327
|
-
# An AST representation of the contents of this node
|
328
|
-
#
|
329
|
-
# @return [Array]
|
330
|
-
def ast_contents()
|
331
|
-
if has_children?
|
332
|
-
@children.map {|child| child.to_ast}
|
333
|
-
else
|
334
|
-
[]
|
335
|
-
end
|
336
|
-
end
|
337
|
-
|
338
|
-
# Create an AST representation of this Node
|
339
|
-
#
|
340
|
-
# @return [Hash]
|
341
|
-
def to_ast()
|
342
|
-
{
|
343
|
-
"t" => ast_type,
|
344
|
-
"c" => ast_contents
|
345
|
-
}
|
346
|
-
end
|
347
|
-
|
348
|
-
# Get the markdown representation of this Node, including the Node
|
349
|
-
# itself.
|
350
|
-
#
|
351
|
-
# @return [String] the outer markdown representation of this Node
|
352
|
-
def markdown()
|
353
|
-
temp_doc = PandocFilter::Document.fragment [self]
|
354
|
-
markdown = AST2MARKDOWN << temp_doc.to_JSON
|
355
|
-
markdown
|
356
|
-
end
|
357
|
-
|
358
|
-
alias outer_markdown markdown
|
359
|
-
|
360
|
-
# Set the markdown representation of this Node: replace this Node
|
361
|
-
# by the Node represented by the markdown string. If an inline
|
362
|
-
# node is being replaced and the replacement has more than one
|
363
|
-
# paragraph, only the contents of the first paragraph is used
|
364
|
-
#
|
365
|
-
# @param markdown [String] the markdown string to replace this
|
366
|
-
# Node
|
367
|
-
#
|
368
|
-
# @example Replacing all horizontal lines by a Plain node saying "hi"
|
369
|
-
# Paru::Filter.run do
|
370
|
-
# with "HorizontalLine" do |line|
|
371
|
-
# line.markdown = "hi"
|
372
|
-
# end
|
373
|
-
# end
|
374
|
-
#
|
375
|
-
def markdown=(markdown)
|
376
|
-
json = MARKDOWN2JSON << markdown
|
377
|
-
temp_doc = PandocFilter::Document.from_JSON json
|
378
|
-
|
379
|
-
if not has_parent? or is_root?
|
380
|
-
@children = temp_doc.children
|
381
|
-
else
|
382
|
-
# replace current node by new nodes
|
383
|
-
# There is a difference between inline and block nodes
|
384
|
-
current_index = parent.find_index self
|
385
|
-
|
386
|
-
# By default, pandoc creates a Block level node when
|
387
|
-
# converting a string. However, if the original is a
|
388
|
-
# inline level node, so should its replacement node(s) be.
|
389
|
-
# Only using first block node (paragraph?)
|
390
|
-
if is_inline?
|
391
|
-
temp_doc = temp_doc.children.first
|
392
|
-
|
393
|
-
if not temp_doc.children.all? {|node| node.is_inline?}
|
394
|
-
raise Error.new "Cannot replace the inline level node represented by '#{self.markdown}' with markdown that converts to block level nodes: '#{markdown}'."
|
395
|
-
end
|
396
|
-
else
|
397
|
-
replacement = temp_doc.children.first
|
398
|
-
@replacement = replacement unless replacement.nil? or to_ast == replacement.to_ast
|
399
|
-
end
|
400
|
-
|
401
|
-
index = current_index
|
402
|
-
temp_doc.each do |child|
|
403
|
-
index += 1
|
404
|
-
parent.insert index, child
|
405
|
-
end
|
406
|
-
|
407
|
-
|
408
|
-
# Remove the original node
|
409
|
-
parent.remove_at current_index
|
410
|
-
end
|
411
|
-
end
|
25
|
+
# PandocFilter is a module containig the paru's Filter functionality
|
26
|
+
module PandocFilter
|
27
|
+
# A Paru::Pandoc converter from JSON to markdown
|
28
|
+
AST2MARKDOWN = Paru::Pandoc.new do
|
29
|
+
from 'json'
|
30
|
+
to 'markdown-smart'
|
31
|
+
preserve_tabs true
|
32
|
+
end
|
412
33
|
|
413
|
-
|
34
|
+
# A Paru::Pandoc converter from markdown to JSON
|
35
|
+
MARKDOWN2JSON = Paru::Pandoc.new do
|
36
|
+
from 'markdown+smart'
|
37
|
+
to 'json'
|
38
|
+
preserve_tabs true
|
39
|
+
end
|
414
40
|
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
41
|
+
# Every node in a Pandoc AST is mapped to Node. Filters are all about
|
42
|
+
# manipulating Nodes.
|
43
|
+
#
|
44
|
+
# @!attribute parent
|
45
|
+
# @return [Node] the parent node, if any.
|
46
|
+
class Node
|
47
|
+
include Enumerable
|
48
|
+
include ASTManipulation
|
49
|
+
|
50
|
+
attr_accessor :parent
|
51
|
+
|
52
|
+
# Block level nodes
|
53
|
+
require_relative 'block_quote'
|
54
|
+
require_relative 'block'
|
55
|
+
require_relative 'bullet_list'
|
56
|
+
require_relative 'code_block'
|
57
|
+
require_relative 'definition_list_item'
|
58
|
+
require_relative 'definition_list'
|
59
|
+
require_relative 'div'
|
60
|
+
require_relative 'empty_block'
|
61
|
+
require_relative 'header'
|
62
|
+
require_relative 'horizontal_rule'
|
63
|
+
require_relative 'line_block'
|
64
|
+
require_relative 'null'
|
65
|
+
require_relative 'ordered_list'
|
66
|
+
require_relative 'para'
|
67
|
+
require_relative 'plain'
|
68
|
+
require_relative 'raw_block'
|
69
|
+
require_relative 'table'
|
70
|
+
require_relative 'caption'
|
71
|
+
require_relative 'table_head'
|
72
|
+
require_relative 'table_foot'
|
73
|
+
require_relative 'row'
|
74
|
+
require_relative 'cell'
|
75
|
+
require_relative 'figure'
|
76
|
+
|
77
|
+
# Inline level nodes
|
78
|
+
require_relative 'cite'
|
79
|
+
require_relative 'code'
|
80
|
+
require_relative 'emph'
|
81
|
+
require_relative 'empty_inline'
|
82
|
+
require_relative 'image'
|
83
|
+
require_relative 'inline'
|
84
|
+
require_relative 'line_break'
|
85
|
+
require_relative 'link'
|
86
|
+
require_relative 'math'
|
87
|
+
require_relative 'note'
|
88
|
+
require_relative 'quoted'
|
89
|
+
require_relative 'raw_inline'
|
90
|
+
require_relative 'small_caps'
|
91
|
+
require_relative 'soft_break'
|
92
|
+
require_relative 'space'
|
93
|
+
require_relative 'span'
|
94
|
+
require_relative 'strikeout'
|
95
|
+
require_relative 'strong'
|
96
|
+
require_relative 'str'
|
97
|
+
require_relative 'subscript'
|
98
|
+
require_relative 'superscript'
|
99
|
+
require_relative 'short_caption'
|
100
|
+
require_relative 'underline'
|
101
|
+
|
102
|
+
# Metadata level nodes
|
103
|
+
require_relative 'meta_blocks'
|
104
|
+
require_relative 'meta_bool'
|
105
|
+
require_relative 'meta_inlines'
|
106
|
+
require_relative 'meta_list'
|
107
|
+
require_relative 'meta_map'
|
108
|
+
require_relative 'meta_string'
|
109
|
+
|
110
|
+
# Create a new Node with contents. Also indicate if this node has
|
111
|
+
# inline children or block children.
|
112
|
+
#
|
113
|
+
# @param contents [Array<pandoc node in JSON> = []] the contents of
|
114
|
+
# this node
|
115
|
+
# @param inline_children [Boolean] does this node have
|
116
|
+
# inline children (true) or block children (false).
|
117
|
+
def initialize(contents = [], inline_children = false)
|
118
|
+
@children = []
|
119
|
+
@parent = nil
|
120
|
+
|
121
|
+
return unless contents.is_a? Array
|
122
|
+
|
123
|
+
contents.each do |elt|
|
124
|
+
child = if PandocFilter.const_defined? elt['t']
|
125
|
+
PandocFilter.const_get(elt['t']).new elt['c']
|
126
|
+
elsif inline_children
|
127
|
+
PandocFilter::Inline.new elt['c']
|
128
|
+
else
|
129
|
+
PandocFilter::Plain.new elt['c']
|
130
|
+
end
|
131
|
+
|
132
|
+
child.parent = self
|
133
|
+
@children.push child
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Create a new node from a markdown string. This is always a block
|
138
|
+
# level node. If more
|
139
|
+
# than one new node is created, a {Div} is created as a parent for
|
140
|
+
# the newly created block nodes..
|
141
|
+
#
|
142
|
+
# @param markdown_string [String] the markdown string to convert
|
143
|
+
# to a AST node
|
144
|
+
#
|
145
|
+
# @return [Block|Div] The {Block} node created by converting
|
146
|
+
# markdown_string with pandoc; A {Div} node if this conversion
|
147
|
+
# holds more than one {Block} node.
|
148
|
+
def self.from_markdown(markdown_string)
|
149
|
+
node = Node.new []
|
150
|
+
node.outer_markdown = markdown_string
|
151
|
+
|
152
|
+
if node.children.size == 1
|
153
|
+
node = node.children.first
|
154
|
+
else
|
155
|
+
container = from_markdown '<div></div>'
|
156
|
+
container.children = node.children
|
157
|
+
node = container
|
158
|
+
end
|
422
159
|
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
160
|
+
node
|
161
|
+
end
|
162
|
+
|
163
|
+
# For each child of this Node, yield the child
|
164
|
+
#
|
165
|
+
# @yield [Node]
|
166
|
+
def each(&block)
|
167
|
+
@children.each(&block)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Does this node have any children?
|
171
|
+
#
|
172
|
+
# @return [Boolean] True if this node has any children, false
|
173
|
+
# otherwise.
|
174
|
+
def has_children?
|
175
|
+
defined? @children and !@children.nil? and @children.size.positive?
|
176
|
+
end
|
177
|
+
|
178
|
+
# Get this node's children,
|
179
|
+
#
|
180
|
+
# @return [Array<Node>] this node's children as an Array.
|
181
|
+
def children
|
182
|
+
if has_children?
|
183
|
+
@children
|
184
|
+
else
|
185
|
+
[]
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# Set this node's children
|
190
|
+
#
|
191
|
+
# @param list [Array<Node>] a list with nodes
|
192
|
+
attr_writer :children
|
193
|
+
|
194
|
+
# Does this node have a parent?
|
195
|
+
#
|
196
|
+
# @return [Boolean] True if this node has a parent, false
|
197
|
+
# otherwise.
|
198
|
+
def has_parent?
|
199
|
+
!@parent.nil?
|
200
|
+
end
|
201
|
+
|
202
|
+
# Is this a root node?
|
203
|
+
#
|
204
|
+
# @return [Boolean] True if this node has a no parent, false
|
205
|
+
# otherwise
|
206
|
+
def is_root?
|
207
|
+
!has_parent?
|
208
|
+
end
|
209
|
+
|
210
|
+
# Is this Node a Node or a leaf? See #is_leaf?
|
211
|
+
#
|
212
|
+
# @return [Boolean] A node is a node if it is not a leaf.
|
213
|
+
def is_node?
|
214
|
+
!is_leaf
|
215
|
+
end
|
216
|
+
|
217
|
+
# Is this Node a leaf? See also #is_node?
|
218
|
+
#
|
219
|
+
# @return [Boolean] A node is a leaf when it has no children
|
220
|
+
# false otherwise
|
221
|
+
def is_leaf?
|
222
|
+
!has_children?
|
223
|
+
end
|
224
|
+
|
225
|
+
# Does this node has a string value?
|
226
|
+
#
|
227
|
+
# @return [Boolean] true if this node has a string value, false
|
228
|
+
# otherwise
|
229
|
+
def has_string?
|
230
|
+
false
|
231
|
+
end
|
232
|
+
|
233
|
+
# Does this node have Inline contents?
|
234
|
+
#
|
235
|
+
# @return [Boolean] true if this node has Inline contents, false
|
236
|
+
# otherwise
|
237
|
+
def has_inline?
|
238
|
+
false
|
239
|
+
end
|
240
|
+
|
241
|
+
# Does this node have Block contents?
|
242
|
+
#
|
243
|
+
# @return [Boolean] true if this node has Block contents, false
|
244
|
+
# otherwise
|
245
|
+
def has_block?
|
246
|
+
false
|
247
|
+
end
|
248
|
+
|
249
|
+
# Is this node a Block level node?
|
250
|
+
#
|
251
|
+
# @return [Boolean] true if this node is a block level node, false
|
252
|
+
# otherwise
|
253
|
+
def is_block?
|
254
|
+
false
|
255
|
+
end
|
256
|
+
|
257
|
+
# Can this node act both as a block and inline node? Some nodes
|
258
|
+
# are hybrids in this regard, like Math or Image
|
259
|
+
#
|
260
|
+
# @return [Boolean]
|
261
|
+
def can_act_as_both_block_and_inline?
|
262
|
+
false
|
263
|
+
end
|
264
|
+
|
265
|
+
# Is this an Inline level node?
|
266
|
+
#
|
267
|
+
# @return [Boolean] true if this node is an inline level node,
|
268
|
+
# false otherwise
|
269
|
+
def is_inline?
|
270
|
+
false
|
271
|
+
end
|
272
|
+
|
273
|
+
# Convert this Node to a metadata value. If this Node
|
274
|
+
# {is_inline?}, it is converted to {MetaInlines} if it is
|
275
|
+
# {is_block?}, it is converted to {MetaBlocks}.
|
276
|
+
#
|
277
|
+
# @return [MetaInlines|MetaBlocks]
|
278
|
+
def toMetadata
|
279
|
+
if is_inline?
|
280
|
+
MetaInlines.new to_ast, true
|
281
|
+
elsif is_blocks?
|
282
|
+
MetaBlocks.new to_ast, false
|
283
|
+
else
|
284
|
+
# ?
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
# If this node has attributes with classes, is name among them?
|
289
|
+
#
|
290
|
+
# @param name [String] the class name to search for
|
291
|
+
#
|
292
|
+
# @return [Boolean] true if this node has attributes with classes
|
293
|
+
# and name is among them, false otherwise
|
294
|
+
def has_class?(name)
|
295
|
+
if @attr.nil?
|
296
|
+
false
|
297
|
+
else
|
298
|
+
@attr.has_class? name
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
# A String representation of this Node
|
303
|
+
#
|
304
|
+
# @return [String]
|
305
|
+
def to_s
|
306
|
+
self.class.name
|
307
|
+
end
|
308
|
+
|
309
|
+
# The pandoc type of this Node
|
310
|
+
#
|
311
|
+
# @return [String]
|
312
|
+
def type
|
313
|
+
ast_type
|
314
|
+
end
|
315
|
+
|
316
|
+
# The AST type of this Node
|
317
|
+
#
|
318
|
+
# @return [String]
|
319
|
+
def ast_type
|
320
|
+
self.class.name.split('::').last
|
321
|
+
end
|
322
|
+
|
323
|
+
# An AST representation of the contents of this node
|
324
|
+
#
|
325
|
+
# @return [Array]
|
326
|
+
def ast_contents
|
327
|
+
if has_children?
|
328
|
+
@children.map(&:to_ast)
|
329
|
+
else
|
330
|
+
[]
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
# Create an AST representation of this Node
|
335
|
+
#
|
336
|
+
# @return [Hash]
|
337
|
+
def to_ast
|
338
|
+
{
|
339
|
+
't' => ast_type,
|
340
|
+
'c' => ast_contents
|
341
|
+
}
|
342
|
+
end
|
343
|
+
|
344
|
+
# Get the markdown representation of this Node, including the Node
|
345
|
+
# itself.
|
346
|
+
#
|
347
|
+
# @return [String] the outer markdown representation of this Node
|
348
|
+
def markdown
|
349
|
+
temp_doc = PandocFilter::Document.fragment [self]
|
350
|
+
AST2MARKDOWN << temp_doc.to_JSON
|
351
|
+
end
|
352
|
+
|
353
|
+
alias outer_markdown markdown
|
354
|
+
|
355
|
+
# Set the markdown representation of this Node: replace this Node
|
356
|
+
# by the Node represented by the markdown string. If an inline
|
357
|
+
# node is being replaced and the replacement has more than one
|
358
|
+
# paragraph, only the contents of the first paragraph is used
|
359
|
+
#
|
360
|
+
# @param markdown [String] the markdown string to replace this
|
361
|
+
# Node
|
362
|
+
#
|
363
|
+
# @example Replacing all horizontal lines by a Plain node saying "hi"
|
364
|
+
# Paru::Filter.run do
|
365
|
+
# with "HorizontalLine" do |line|
|
366
|
+
# line.markdown = "hi"
|
367
|
+
# end
|
368
|
+
# end
|
369
|
+
#
|
370
|
+
def markdown=(markdown)
|
371
|
+
json = MARKDOWN2JSON << markdown
|
372
|
+
temp_doc = PandocFilter::Document.from_JSON json
|
373
|
+
|
374
|
+
if !has_parent? || is_root?
|
375
|
+
@children = temp_doc.children
|
376
|
+
else
|
377
|
+
# replace current node by new nodes
|
378
|
+
# There is a difference between inline and block nodes
|
379
|
+
current_index = parent.find_index self
|
380
|
+
|
381
|
+
# By default, pandoc creates a Block level node when
|
382
|
+
# converting a string. However, if the original is a
|
383
|
+
# inline level node, so should its replacement node(s) be.
|
384
|
+
# Only using first block node (paragraph?)
|
385
|
+
if is_inline?
|
386
|
+
temp_doc = temp_doc.children.first
|
387
|
+
|
388
|
+
unless temp_doc.children.all?(&:is_inline?)
|
389
|
+
raise Error,
|
390
|
+
"Cannot replace the inline level node represented by '#{self.markdown}' with markdown that converts to block level nodes: '#{markdown}'."
|
429
391
|
end
|
430
|
-
|
392
|
+
else
|
393
|
+
replacement = temp_doc.children.first
|
394
|
+
@replacement = replacement unless replacement.nil? || (to_ast == replacement.to_ast)
|
395
|
+
end
|
396
|
+
|
397
|
+
index = current_index
|
398
|
+
temp_doc.each do |child|
|
399
|
+
index += 1
|
400
|
+
parent.insert index, child
|
401
|
+
end
|
402
|
+
|
403
|
+
# Remove the original node
|
404
|
+
parent.remove_at current_index
|
431
405
|
end
|
406
|
+
end
|
407
|
+
|
408
|
+
alias outer_markdown= markdown=
|
409
|
+
|
410
|
+
# Has this node been replaced by using the {markdown} method? If
|
411
|
+
# so, return true.
|
412
|
+
#
|
413
|
+
# @return [Boolean]
|
414
|
+
def has_been_replaced?
|
415
|
+
!@replacement.nil?
|
416
|
+
end
|
417
|
+
|
418
|
+
# Get this node's replacemnt. Nil if it has not been replaced by
|
419
|
+
# the {markdown} method
|
420
|
+
#
|
421
|
+
# @return [Node] This node's replacement or nil if there is none.
|
422
|
+
def get_replacement
|
423
|
+
@replacement
|
424
|
+
end
|
432
425
|
end
|
426
|
+
end
|
433
427
|
end
|