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).



115
116
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 115

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

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

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

Instance Attribute Details

#parentNode

Returns the parent node, if any.

Returns:

  • (Node)

    the parent node, if any.



44
45
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
426
427
428
429
430
431
# File 'lib/paru/filter/node.rb', line 44

class Node
    include Enumerable
    include ASTManipulation

    attr_accessor :parent

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

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

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

    # 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

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

                child.parent = self
                @children.push child
            end
        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

        return node 
    end

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

    # Does this node have any children?
    #
    # @return [Boolean] True if this node has any children, false
    #   otherwise.
    def has_children?()
        defined? @children and not @children.nil? and @children.size > 0
    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
    def children=(list)
        @children = list
    end

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

    # Is this a root node? 
    #
    # @return [Boolean] True if this node has a no parent, false 
    #   otherwise
    def is_root?()
        not 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?()
        not 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?()
        not 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? then
            MetaInlines.new to_ast, true
        elsif is_blocks? then
            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 not @attr.nil?
            @attr.has_class? name
        else
            false
        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 {|child| child.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]
        markdown = AST2MARKDOWN << temp_doc.to_JSON
        markdown
    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 not has_parent? or 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

                if not temp_doc.children.all? {|node| node.is_inline?}
                    raise Error.new "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? or 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?
        not @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

    return 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)


330
331
332
333
334
335
336
# File 'lib/paru/filter/node.rb', line 330

def ast_contents()
    if has_children?
        @children.map {|child| child.to_ast}
    else
        []
    end
end

#ast_typeString

The AST type of this Node

Returns:

  • (String)


323
324
325
# File 'lib/paru/filter/node.rb', line 323

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)


265
266
267
# File 'lib/paru/filter/node.rb', line 265

def can_act_as_both_block_and_inline?()
    false
end

#childrenArray<Node>

Get this node's children,

Returns:

  • (Array<Node>)

    this node's children as an Array.



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

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

#children=(list) ⇒ Object

Set this node's children

Parameters:

  • list (Array<Node>)

    a list with nodes



194
195
196
# File 'lib/paru/filter/node.rb', line 194

def children=(list)
    @children = list
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
169
170
# File 'lib/paru/filter/node.rb', line 166

def each()
    @children.each do |child|
        yield child
    end
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.



427
428
429
# File 'lib/paru/filter/node.rb', line 427

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)


419
420
421
# File 'lib/paru/filter/node.rb', line 419

def has_been_replaced?
    not @replacement.nil?
end

#has_block?Boolean

Does this node have Block contents?

Returns:

  • (Boolean)

    true if this node has Block contents, false otherwise



249
250
251
# File 'lib/paru/filter/node.rb', line 249

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.



176
177
178
# File 'lib/paru/filter/node.rb', line 176

def has_children?()
    defined? @children and not @children.nil? and @children.size > 0
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



298
299
300
301
302
303
304
# File 'lib/paru/filter/node.rb', line 298

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

#has_inline?Boolean

Does this node have Inline contents?

Returns:

  • (Boolean)

    true if this node has Inline contents, false otherwise



241
242
243
# File 'lib/paru/filter/node.rb', line 241

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.



202
203
204
# File 'lib/paru/filter/node.rb', line 202

def has_parent?()
    not @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



233
234
235
# File 'lib/paru/filter/node.rb', line 233

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



257
258
259
# File 'lib/paru/filter/node.rb', line 257

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



273
274
275
# File 'lib/paru/filter/node.rb', line 273

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



225
226
227
# File 'lib/paru/filter/node.rb', line 225

def is_leaf?()
    not 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.



217
218
219
# File 'lib/paru/filter/node.rb', line 217

def is_node?()
    not is_leaf
end

#is_root?Boolean

Is this a root node?

Returns:

  • (Boolean)

    True if this node has a no parent, false otherwise



210
211
212
# File 'lib/paru/filter/node.rb', line 210

def is_root?()
    not 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



352
353
354
355
356
# File 'lib/paru/filter/node.rb', line 352

def markdown()
    temp_doc = PandocFilter::Document.fragment [self]
    markdown = AST2MARKDOWN << temp_doc.to_JSON
    markdown
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



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
# File 'lib/paru/filter/node.rb', line 375

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

    if not has_parent? or 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

            if not temp_doc.children.all? {|node| node.is_inline?}
                raise Error.new "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? or 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)


341
342
343
344
345
346
# File 'lib/paru/filter/node.rb', line 341

def to_ast()
    {
        "t" => ast_type,
        "c" => ast_contents
    }
end

#to_sString

A String representation of this Node

Returns:

  • (String)


309
310
311
# File 'lib/paru/filter/node.rb', line 309

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:



282
283
284
285
286
287
288
289
290
# File 'lib/paru/filter/node.rb', line 282

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

#typeString

The pandoc type of this Node

Returns:

  • (String)


316
317
318
# File 'lib/paru/filter/node.rb', line 316

def type()
    ast_type
end