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.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/lib/paru/error.rb +6 -4
  3. data/lib/paru/filter/ast_manipulation.rb +90 -91
  4. data/lib/paru/filter/attr.rb +75 -69
  5. data/lib/paru/filter/block.rb +15 -14
  6. data/lib/paru/filter/block_quote.rb +14 -12
  7. data/lib/paru/filter/bullet_list.rb +17 -16
  8. data/lib/paru/filter/caption.rb +50 -48
  9. data/lib/paru/filter/cell.rb +52 -50
  10. data/lib/paru/filter/citation.rb +53 -51
  11. data/lib/paru/filter/cite.rb +34 -33
  12. data/lib/paru/filter/code.rb +51 -49
  13. data/lib/paru/filter/code_block.rb +76 -76
  14. data/lib/paru/filter/col_spec.rb +58 -56
  15. data/lib/paru/filter/definition_list.rb +51 -52
  16. data/lib/paru/filter/definition_list_item.rb +45 -43
  17. data/lib/paru/filter/div.rb +37 -35
  18. data/lib/paru/filter/document.rb +112 -115
  19. data/lib/paru/filter/emph.rb +7 -5
  20. data/lib/paru/filter/empty_block.rb +17 -16
  21. data/lib/paru/filter/empty_inline.rb +23 -22
  22. data/lib/paru/filter/figure.rb +41 -39
  23. data/lib/paru/filter/header.rb +41 -39
  24. data/lib/paru/filter/horizontal_rule.rb +7 -5
  25. data/lib/paru/filter/image.rb +13 -12
  26. data/lib/paru/filter/inline.rb +27 -26
  27. data/lib/paru/filter/inner_markdown.rb +60 -62
  28. data/lib/paru/filter/int_value.rb +19 -18
  29. data/lib/paru/filter/line_block.rb +13 -11
  30. data/lib/paru/filter/line_break.rb +7 -5
  31. data/lib/paru/filter/link.rb +34 -33
  32. data/lib/paru/filter/list.rb +37 -37
  33. data/lib/paru/filter/list_attributes.rb +52 -51
  34. data/lib/paru/filter/math.rb +66 -64
  35. data/lib/paru/filter/meta.rb +40 -39
  36. data/lib/paru/filter/meta_blocks.rb +7 -5
  37. data/lib/paru/filter/meta_bool.rb +7 -5
  38. data/lib/paru/filter/meta_inlines.rb +9 -7
  39. data/lib/paru/filter/meta_list.rb +7 -5
  40. data/lib/paru/filter/meta_map.rb +50 -49
  41. data/lib/paru/filter/meta_string.rb +7 -6
  42. data/lib/paru/filter/meta_value.rb +26 -25
  43. data/lib/paru/filter/metadata.rb +150 -88
  44. data/lib/paru/filter/node.rb +400 -406
  45. data/lib/paru/filter/note.rb +29 -29
  46. data/lib/paru/filter/null.rb +7 -5
  47. data/lib/paru/filter/ordered_list.rb +50 -49
  48. data/lib/paru/filter/para.rb +21 -20
  49. data/lib/paru/filter/plain.rb +23 -21
  50. data/lib/paru/filter/quoted.rb +28 -26
  51. data/lib/paru/filter/short_caption.rb +7 -5
  52. data/lib/paru/filter/small_caps.rb +8 -7
  53. data/lib/paru/filter/soft_break.rb +7 -5
  54. data/lib/paru/filter/space.rb +7 -5
  55. data/lib/paru/filter/span.rb +29 -27
  56. data/lib/paru/filter/str.rb +33 -32
  57. data/lib/paru/filter/strikeout.rb +7 -6
  58. data/lib/paru/filter/strong.rb +7 -6
  59. data/lib/paru/filter/subscript.rb +7 -6
  60. data/lib/paru/filter/superscript.rb +7 -6
  61. data/lib/paru/filter/table.rb +201 -210
  62. data/lib/paru/filter/table_body.rb +67 -67
  63. data/lib/paru/filter/table_end.rb +53 -55
  64. data/lib/paru/filter/table_foot.rb +8 -7
  65. data/lib/paru/filter/table_head.rb +8 -7
  66. data/lib/paru/filter/target.rb +29 -27
  67. data/lib/paru/filter/underline.rb +7 -5
  68. data/lib/paru/filter/value.rb +74 -75
  69. data/lib/paru/filter/version.rb +23 -22
  70. data/lib/paru/filter.rb +355 -331
  71. data/lib/paru/filter_error.rb +7 -5
  72. data/lib/paru/info.rb +29 -30
  73. data/lib/paru/pandoc.rb +241 -248
  74. data/lib/paru/pandoc2yaml.rb +51 -42
  75. data/lib/paru/selector.rb +193 -184
  76. data/lib/paru.rb +3 -1
  77. metadata +4 -73
@@ -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 "../pandoc.rb"
20
- require_relative './ast_manipulation.rb'
21
+ require_relative '../pandoc'
22
+ require_relative 'ast_manipulation'
21
23
 
22
24
  module Paru
23
- # PandocFilter is a module containig the paru's Filter functionality
24
- module PandocFilter
25
- # A Paru::Pandoc converter from JSON to markdown
26
- AST2MARKDOWN = Paru::Pandoc.new do
27
- from "json"
28
- to "markdown-smart"
29
- preserve_tabs true
30
- end
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
- alias outer_markdown= markdown=
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
- # Has this node been replaced by using the {markdown} method? If
416
- # so, return true.
417
- #
418
- # @return [Boolean]
419
- def has_been_replaced?
420
- not @replacement.nil?
421
- end
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
- # Get this node's replacemnt. Nil if it has not been replaced by
424
- # the {markdown} method
425
- #
426
- # @return [Node] This node's replacement or nil if there is none.
427
- def get_replacement
428
- @replacement
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