Class: Paru::PandocFilter::Node

Inherits:
Object
  • Object
show all
Includes:
Enumerable, ASTManipulation
Defined in:
lib/paru/filter/node.rb

Overview

Every node in a Pandoc AST is mapped to Node. Filters are all about manipulating Nodes.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(contents = [], inline_children = false) ⇒ Node

Create a new Node with contents. Also indicate if this node has inline children or block children.

Parameters:

  • contents (Array<pandoc node in JSON> = []) (defaults to: [])

    the contents of this node

  • inline_children (Boolean) (defaults to: false)

    does this node have inline children (true) or block children (false).



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/paru/filter/node.rb', line 117

def initialize(contents = [], inline_children = false)
  @children = []
  @parent = nil

  return unless contents.is_a? Array

  contents.each do |elt|
    child = if PandocFilter.const_defined? elt['t']
              PandocFilter.const_get(elt['t']).new elt['c']
            elsif inline_children
              PandocFilter::Inline.new elt['c']
            else
              PandocFilter::Plain.new elt['c']
            end

    child.parent = self
    @children.push child
  end
end

Instance Attribute Details

#childrenArray<Node>

Get this node’s children,

Returns:

  • (Array<Node>)

    this node’s children as an Array.



181
182
183
184
185
186
187
# File 'lib/paru/filter/node.rb', line 181

def children
  if has_children?
    @children
  else
    []
  end
end

#parentNode

Returns the parent node, if any.

Returns:

  • (Node)

    the parent node, if any.



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
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
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
# File 'lib/paru/filter/node.rb', line 46

class Node
  include Enumerable
  include ASTManipulation

  attr_accessor :parent

  # Block level nodes
  require_relative 'block_quote'
  require_relative 'block'
  require_relative 'bullet_list'
  require_relative 'code_block'
  require_relative 'definition_list_item'
  require_relative 'definition_list'
  require_relative 'div'
  require_relative 'empty_block'
  require_relative 'header'
  require_relative 'horizontal_rule'
  require_relative 'line_block'
  require_relative 'null'
  require_relative 'ordered_list'
  require_relative 'para'
  require_relative 'plain'
  require_relative 'raw_block'
  require_relative 'table'
  require_relative 'caption'
  require_relative 'table_head'
  require_relative 'table_foot'
  require_relative 'row'
  require_relative 'cell'
  require_relative 'figure'

  # Inline level nodes
  require_relative 'cite'
  require_relative 'code'
  require_relative 'emph'
  require_relative 'empty_inline'
  require_relative 'image'
  require_relative 'inline'
  require_relative 'line_break'
  require_relative 'link'
  require_relative 'math'
  require_relative 'note'
  require_relative 'quoted'
  require_relative 'raw_inline'
  require_relative 'small_caps'
  require_relative 'soft_break'
  require_relative 'space'
  require_relative 'span'
  require_relative 'strikeout'
  require_relative 'strong'
  require_relative 'str'
  require_relative 'subscript'
  require_relative 'superscript'
  require_relative 'short_caption'
  require_relative 'underline'

  # Metadata level nodes
  require_relative 'meta_blocks'
  require_relative 'meta_bool'
  require_relative 'meta_inlines'
  require_relative 'meta_list'
  require_relative 'meta_map'
  require_relative 'meta_string'

  # Create a new Node with contents. Also indicate if this node has
  # inline children or block children.
  #
  # @param contents [Array<pandoc node in JSON> = []] the contents of
  #   this node
  # @param inline_children [Boolean] does this node have
  #   inline children (true) or block children (false).
  def initialize(contents = [], inline_children = false)
    @children = []
    @parent = nil

    return unless contents.is_a? Array

    contents.each do |elt|
      child = if PandocFilter.const_defined? elt['t']
                PandocFilter.const_get(elt['t']).new elt['c']
              elsif inline_children
                PandocFilter::Inline.new elt['c']
              else
                PandocFilter::Plain.new elt['c']
              end

      child.parent = self
      @children.push child
    end
  end

  # Create a new node from a markdown string. This is always a block
  # level node. If more
  # than one new node is created, a {Div} is created as a parent for
  # the newly created block nodes..
  #
  # @param markdown_string [String] the markdown string to convert
  #   to a AST node
  #
  # @return [Block|Div] The {Block} node created by converting
  #   markdown_string with pandoc; A {Div} node if this conversion
  #   holds more than one {Block} node.
  def self.from_markdown(markdown_string)
    node = Node.new []
    node.outer_markdown = markdown_string

    if node.children.size == 1
      node = node.children.first
    else
      container = from_markdown '<div></div>'
      container.children = node.children
      node = container
    end

    node
  end

  # For each child of this Node, yield the child
  #
  # @yield [Node]
  def each(&block)
    @children.each(&block)
  end

  # Does this node have any children?
  #
  # @return [Boolean] True if this node has any children, false
  #   otherwise.
  def has_children?
    defined? @children and !@children.nil? and @children.size.positive?
  end

  # Get this node's children,
  #
  # @return [Array<Node>] this node's children as an Array.
  def children
    if has_children?
      @children
    else
      []
    end
  end

  # Set this node's children
  #
  # @param list [Array<Node>] a list with nodes
  attr_writer :children

  # Does this node have a parent?
  #
  # @return [Boolean] True if this node has a parent, false
  #   otherwise.
  def has_parent?
    !@parent.nil?
  end

  # Is this a root node?
  #
  # @return [Boolean] True if this node has a no parent, false
  #   otherwise
  def is_root?
    !has_parent?
  end

  # Is this Node a Node or a leaf? See #is_leaf?
  #
  # @return [Boolean] A node is a node if it is not a leaf.
  def is_node?
    !is_leaf
  end

  # Is this Node a leaf? See also #is_node?
  #
  # @return [Boolean] A node is a leaf when it has no children
  #   false otherwise
  def is_leaf?
    !has_children?
  end

  # Does this node has a string value?
  #
  # @return [Boolean] true if this node has a string value, false
  #   otherwise
  def has_string?
    false
  end

  # Does this node have Inline contents?
  #
  # @return [Boolean] true if this node has Inline contents, false
  #   otherwise
  def has_inline?
    false
  end

  # Does this node have Block contents?
  #
  # @return [Boolean] true if this node has Block contents, false
  #   otherwise
  def has_block?
    false
  end

  # Is this node a Block level node?
  #
  # @return [Boolean] true if this node is a block level node, false
  #   otherwise
  def is_block?
    false
  end

  # Can this node act both as a block and inline node? Some nodes
  # are hybrids in this regard, like Math or Image
  #
  # @return [Boolean]
  def can_act_as_both_block_and_inline?
    false
  end

  # Is this an Inline level node?
  #
  # @return [Boolean] true if this node is an inline level node,
  #   false otherwise
  def is_inline?
    false
  end

  # Convert this Node to a metadata value. If this Node
  # {is_inline?}, it is converted to {MetaInlines} if it is
  # {is_block?}, it is converted to {MetaBlocks}.
  #
  # @return [MetaInlines|MetaBlocks]
  def 
    if is_inline?
      MetaInlines.new to_ast, true
    elsif is_blocks?
      MetaBlocks.new to_ast, false
    else
      # ?
    end
  end

  # If this node has attributes with classes, is name among them?
  #
  # @param name [String] the class name to search for
  #
  # @return [Boolean] true if this node has attributes with classes
  #   and name is among them, false otherwise
  def has_class?(name)
    if @attr.nil?
      false
    else
      @attr.has_class? name
    end
  end

  # A String representation of this Node
  #
  # @return [String]
  def to_s
    self.class.name
  end

  # The pandoc type of this Node
  #
  # @return [String]
  def type
    ast_type
  end

  # The AST type of this Node
  #
  # @return [String]
  def ast_type
    self.class.name.split('::').last
  end

  # An AST representation of the contents of this node
  #
  # @return [Array]
  def ast_contents
    if has_children?
      @children.map(&:to_ast)
    else
      []
    end
  end

  # Create an AST representation of this Node
  #
  # @return [Hash]
  def to_ast
    {
      't' => ast_type,
      'c' => ast_contents
    }
  end

  # Get the markdown representation of this Node, including the Node
  # itself.
  #
  # @return [String] the outer markdown representation of this Node
  def markdown
    temp_doc = PandocFilter::Document.fragment [self]
    AST2MARKDOWN << temp_doc.to_JSON
  end

  alias outer_markdown markdown

  # Set the markdown representation of this Node: replace this Node
  # by the Node represented by the markdown string. If an inline
  # node is being replaced and the replacement has more than one
  # paragraph, only the contents of the first paragraph is used
  #
  # @param markdown [String] the markdown string to replace this
  #   Node
  #
  # @example Replacing all horizontal lines by a Plain node saying "hi"
  #   Paru::Filter.run do
  #       with "HorizontalLine" do |line|
  #           line.markdown = "hi"
  #       end
  #   end
  #
  def markdown=(markdown)
    json = MARKDOWN2JSON << markdown
    temp_doc = PandocFilter::Document.from_JSON json

    if !has_parent? || is_root?
      @children = temp_doc.children
    else
      # replace current node by new nodes
      # There is a difference between inline and block nodes
      current_index = parent.find_index self

      # By default, pandoc creates a Block level node when
      # converting a string. However, if the original is a
      # inline level node, so should its replacement node(s) be.
      # Only using first block node (paragraph?)
      if is_inline?
        temp_doc = temp_doc.children.first

        unless temp_doc.children.all?(&:is_inline?)
          raise Error,
                "Cannot replace the inline level node represented by '#{self.markdown}' with markdown that converts to block level nodes: '#{markdown}'."
        end
      else
        replacement = temp_doc.children.first
        @replacement = replacement unless replacement.nil? || (to_ast == replacement.to_ast)
      end

      index = current_index
      temp_doc.each do |child|
        index += 1
        parent.insert index, child
      end

      # Remove the original node
      parent.remove_at current_index
    end
  end

  alias outer_markdown= markdown=

  # Has this node been replaced by using the {markdown} method? If
  # so, return true.
  #
  # @return [Boolean]
  def has_been_replaced?
    !@replacement.nil?
  end

  # Get this node's replacemnt. Nil if it has not been replaced by
  # the {markdown} method
  #
  # @return [Node] This node's replacement or nil if there is none.
  def get_replacement
    @replacement
  end
end

Class Method Details

.from_markdown(markdown_string) ⇒ Block|Div

Create a new node from a markdown string. This is always a block level node. If more than one new node is created, a Div is created as a parent for the newly created block nodes..

Parameters:

  • markdown_string (String)

    the markdown string to convert to a AST node

Returns:

  • (Block|Div)

    The Block node created by converting markdown_string with pandoc; A Div node if this conversion holds more than one Block node.



148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/paru/filter/node.rb', line 148

def self.from_markdown(markdown_string)
  node = Node.new []
  node.outer_markdown = markdown_string

  if node.children.size == 1
    node = node.children.first
  else
    container = from_markdown '<div></div>'
    container.children = node.children
    node = container
  end

  node
end

Instance Method Details

#append(child) ⇒ Object Also known as: << Originally defined in module ASTManipulation

Append a child to the list with this node’s children.

Parameters:

  • child (Node)

    the child to append.

#ast_contentsArray

An AST representation of the contents of this node

Returns:

  • (Array)


326
327
328
329
330
331
332
# File 'lib/paru/filter/node.rb', line 326

def ast_contents
  if has_children?
    @children.map(&:to_ast)
  else
    []
  end
end

#ast_typeString

The AST type of this Node

Returns:

  • (String)


319
320
321
# File 'lib/paru/filter/node.rb', line 319

def ast_type
  self.class.name.split('::').last
end

#can_act_as_both_block_and_inline?Boolean

Can this node act both as a block and inline node? Some nodes are hybrids in this regard, like Math or Image

Returns:

  • (Boolean)


261
262
263
# File 'lib/paru/filter/node.rb', line 261

def can_act_as_both_block_and_inline?
  false
end

#delete(child) ⇒ Object Originally defined in module ASTManipulation

Delete child from this node’s children.

Parameters:

  • child (Node)

    the child node to delete.

#each {|Node| ... } ⇒ Object

For each child of this Node, yield the child

Yields:



166
167
168
# File 'lib/paru/filter/node.rb', line 166

def each(&block)
  @children.each(&block)
end

#each_depth_first(&block) {|Node| ... } ⇒ Object Originally defined in module ASTManipulation

Walk the node tree starting at this node, depth first, and apply block to each node in the tree

Parameters:

  • block (Proc)

    the block to apply to each node in this node tree

Yields:

#find_index(child) ⇒ Number Originally defined in module ASTManipulation

Find index of child

Parameters:

  • child (Node)

    the child to find the index for

Returns:

  • (Number)

    the index of child or nil

#get(index) ⇒ Node Originally defined in module ASTManipulation

Get the child node at index

Parameters:

  • index (Number)

    the index of the child to get

Returns:

  • (Node)

    the child at index

#get_replacementNode

Get this node’s replacemnt. Nil if it has not been replaced by the #markdown method

Returns:

  • (Node)

    This node’s replacement or nil if there is none.



422
423
424
# File 'lib/paru/filter/node.rb', line 422

def get_replacement
  @replacement
end

#has_been_replaced?Boolean

Has this node been replaced by using the #markdown method? If so, return true.

Returns:

  • (Boolean)


414
415
416
# File 'lib/paru/filter/node.rb', line 414

def has_been_replaced?
  !@replacement.nil?
end

#has_block?Boolean

Does this node have Block contents?

Returns:

  • (Boolean)

    true if this node has Block contents, false otherwise



245
246
247
# File 'lib/paru/filter/node.rb', line 245

def has_block?
  false
end

#has_children?Boolean

Does this node have any children?

Returns:

  • (Boolean)

    True if this node has any children, false otherwise.



174
175
176
# File 'lib/paru/filter/node.rb', line 174

def has_children?
  defined? @children and !@children.nil? and @children.size.positive?
end

#has_class?(name) ⇒ Boolean

If this node has attributes with classes, is name among them?

Parameters:

  • name (String)

    the class name to search for

Returns:

  • (Boolean)

    true if this node has attributes with classes and name is among them, false otherwise



294
295
296
297
298
299
300
# File 'lib/paru/filter/node.rb', line 294

def has_class?(name)
  if @attr.nil?
    false
  else
    @attr.has_class? name
  end
end

#has_inline?Boolean

Does this node have Inline contents?

Returns:

  • (Boolean)

    true if this node has Inline contents, false otherwise



237
238
239
# File 'lib/paru/filter/node.rb', line 237

def has_inline?
  false
end

#has_parent?Boolean

Does this node have a parent?

Returns:

  • (Boolean)

    True if this node has a parent, false otherwise.



198
199
200
# File 'lib/paru/filter/node.rb', line 198

def has_parent?
  !@parent.nil?
end

#has_string?Boolean

Does this node has a string value?

Returns:

  • (Boolean)

    true if this node has a string value, false otherwise



229
230
231
# File 'lib/paru/filter/node.rb', line 229

def has_string?
  false
end

#insert(index, child) ⇒ Object Originally defined in module ASTManipulation

Insert child node among this node’s children at position index.

Parameters:

  • index (Integer)

    the position to insert the child

  • child (Node)

    the child to insert

#is_block?Boolean

Is this node a Block level node?

Returns:

  • (Boolean)

    true if this node is a block level node, false otherwise



253
254
255
# File 'lib/paru/filter/node.rb', line 253

def is_block?
  false
end

#is_inline?Boolean

Is this an Inline level node?

Returns:

  • (Boolean)

    true if this node is an inline level node, false otherwise



269
270
271
# File 'lib/paru/filter/node.rb', line 269

def is_inline?
  false
end

#is_leaf?Boolean

Is this Node a leaf? See also #is_node?

Returns:

  • (Boolean)

    A node is a leaf when it has no children false otherwise



221
222
223
# File 'lib/paru/filter/node.rb', line 221

def is_leaf?
  !has_children?
end

#is_node?Boolean

Is this Node a Node or a leaf? See #is_leaf?

Returns:

  • (Boolean)

    A node is a node if it is not a leaf.



213
214
215
# File 'lib/paru/filter/node.rb', line 213

def is_node?
  !is_leaf
end

#is_root?Boolean

Is this a root node?

Returns:

  • (Boolean)

    True if this node has a no parent, false otherwise



206
207
208
# File 'lib/paru/filter/node.rb', line 206

def is_root?
  !has_parent?
end

#markdownString Also known as: outer_markdown

Get the markdown representation of this Node, including the Node itself.

Returns:

  • (String)

    the outer markdown representation of this Node



348
349
350
351
# File 'lib/paru/filter/node.rb', line 348

def markdown
  temp_doc = PandocFilter::Document.fragment [self]
  AST2MARKDOWN << temp_doc.to_JSON
end

#markdown=(markdown) ⇒ Object Also known as: outer_markdown=

Set the markdown representation of this Node: replace this Node by the Node represented by the markdown string. If an inline node is being replaced and the replacement has more than one paragraph, only the contents of the first paragraph is used

Examples:

Replacing all horizontal lines by a Plain node saying “hi”

Paru::Filter.run do
    with "HorizontalLine" do |line|
        line.markdown = "hi"
    end
end

Parameters:

  • markdown (String)

    the markdown string to replace this Node



370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
# File 'lib/paru/filter/node.rb', line 370

def markdown=(markdown)
  json = MARKDOWN2JSON << markdown
  temp_doc = PandocFilter::Document.from_JSON json

  if !has_parent? || is_root?
    @children = temp_doc.children
  else
    # replace current node by new nodes
    # There is a difference between inline and block nodes
    current_index = parent.find_index self

    # By default, pandoc creates a Block level node when
    # converting a string. However, if the original is a
    # inline level node, so should its replacement node(s) be.
    # Only using first block node (paragraph?)
    if is_inline?
      temp_doc = temp_doc.children.first

      unless temp_doc.children.all?(&:is_inline?)
        raise Error,
              "Cannot replace the inline level node represented by '#{self.markdown}' with markdown that converts to block level nodes: '#{markdown}'."
      end
    else
      replacement = temp_doc.children.first
      @replacement = replacement unless replacement.nil? || (to_ast == replacement.to_ast)
    end

    index = current_index
    temp_doc.each do |child|
      index += 1
      parent.insert index, child
    end

    # Remove the original node
    parent.remove_at current_index
  end
end

#prepend(child) ⇒ Object Originally defined in module ASTManipulation

Prepend a child to the list with this node’s children.

Parameters:

  • child (Node)

    the child to prepend.

#remove_at(index) ⇒ Object Originally defined in module ASTManipulation

Remove the child at position index from this node’s children

Parameters:

  • index (Integer)

    the position of the child to remove

#replace(old_child, new_child) ⇒ Object Originally defined in module ASTManipulation

Replace a child from this node’s children with a new child.

Parameters:

  • old_child (Node)

    the child to replace

  • new_child (Node)

    the replacement child

#replace_at(index, new_child) ⇒ Object Originally defined in module ASTManipulation

Replace the child at position index from this node’s children with a new child.

Parameters:

  • index (Integer)

    the position of the child to replace

  • new_child (Node)

    the replacement child

#to_astHash

Create an AST representation of this Node

Returns:

  • (Hash)


337
338
339
340
341
342
# File 'lib/paru/filter/node.rb', line 337

def to_ast
  {
    't' => ast_type,
    'c' => ast_contents
  }
end

#to_sString

A String representation of this Node

Returns:

  • (String)


305
306
307
# File 'lib/paru/filter/node.rb', line 305

def to_s
  self.class.name
end

#toMetadataMetaInlines|MetaBlocks

Convert this Node to a metadata value. If this Node #is_inline?, it is converted to MetaInlines if it is #is_block?, it is converted to MetaBlocks.

Returns:



278
279
280
281
282
283
284
285
286
# File 'lib/paru/filter/node.rb', line 278

def 
  if is_inline?
    MetaInlines.new to_ast, true
  elsif is_blocks?
    MetaBlocks.new to_ast, false
  else
    # ?
  end
end

#typeString

The pandoc type of this Node

Returns:

  • (String)


312
313
314
# File 'lib/paru/filter/node.rb', line 312

def type
  ast_type
end