Class: Prosereflect::Fragment

Inherits:
Object
  • Object
show all
Defined in:
lib/prosereflect/fragment.rb

Overview

Fragment represents a sequence of nodes. Used for document content, slice content, etc.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(content = []) ⇒ Fragment

Returns a new instance of Fragment.



9
10
11
12
13
14
15
16
17
# File 'lib/prosereflect/fragment.rb', line 9

def initialize(content = [])
  @content = if content.is_a?(Array)
               content
             elsif content.respond_to?(:to_a)
               content.to_a
             else
               [content]
             end
end

Instance Attribute Details

#contentObject (readonly)

Returns the value of attribute content.



7
8
9
# File 'lib/prosereflect/fragment.rb', line 7

def content
  @content
end

Class Method Details

.emptyObject

Create empty fragment



228
229
230
# File 'lib/prosereflect/fragment.rb', line 228

def self.empty
  @empty ||= new([])
end

.from(content) ⇒ Object

Create from content



233
234
235
236
237
238
239
# File 'lib/prosereflect/fragment.rb', line 233

def self.from(content)
  case content
  when Fragment then content
  when Array then new(content.flatten)
  else new([content])
  end
end

Instance Method Details

#[](index) ⇒ Object

Access by index



206
207
208
# File 'lib/prosereflect/fragment.rb', line 206

def [](index)
  @content[index]
end

#append(other) ⇒ Object

Append another fragment to this one



30
31
32
33
34
35
36
# File 'lib/prosereflect/fragment.rb', line 30

def append(other)
  if other.is_a?(Fragment)
    Fragment.new(@content + other.content)
  else
    Fragment.new(@content + [other])
  end
end

#cut(from = 0, to = nil) ⇒ Object

Cut this fragment to a range



39
40
41
42
43
44
45
# File 'lib/prosereflect/fragment.rb', line 39

def cut(from = 0, to = nil)
  to ||= size

  return Fragment.new([]) if from >= to

  cut_nodes(from, to)
end

#cut_nodes(from, to) ⇒ Object



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/prosereflect/fragment.rb', line 47

def cut_nodes(from, to)
  result = []
  pos = 0

  @content.each do |node|
    node_end = pos + node.node_size

    result << node if in_range_before_from?(pos, node_end, from)
    result << node if overlaps_range?(pos, node_end, from, to)

    pos = node_end
    break if pos >= to
  end

  Fragment.new(result)
end

#descendants(block, node_start = 0) ⇒ Object

Iterate over all descendant nodes



134
135
136
# File 'lib/prosereflect/fragment.rb', line 134

def descendants(block, node_start = 0)
  nodes_between(0, size, block, node_start)
end

#dispatch_node_callback(node, pos, node_end, from, to, callback, node_start) ⇒ Object



96
97
98
99
100
101
102
103
104
# File 'lib/prosereflect/fragment.rb', line 96

def dispatch_node_callback(node, pos, node_end, from, to, callback, node_start)
  if node.text?
    text_node_callback(node, pos, from, node_start, callback)
  elsif node_fully_in_range?(pos, node_end, from, to)
    full_node_callback(node, pos, node_end, from, to, callback, node_start)
  elsif node_overlaps_from?(pos, node_end, from)
    partial_node_callback(node, pos, node_end, from, to, callback, node_start)
  end
end

#each(&block) ⇒ Object

Iterate



211
212
213
# File 'lib/prosereflect/fragment.rb', line 211

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

#empty?Boolean

Check if fragment is empty

Returns:

  • (Boolean)


25
26
27
# File 'lib/prosereflect/fragment.rb', line 25

def empty?
  @content.empty?
end

#eq?(other) ⇒ Boolean Also known as: ==

Check equality

Returns:

  • (Boolean)


191
192
193
194
195
196
# File 'lib/prosereflect/fragment.rb', line 191

def eq?(other)
  return false unless other.is_a?(Fragment)

  @content.length == other.content.length &&
    @content.zip(other.content).all? { |a, b| a.to_h == b.to_h }
end

#find_diff_end(other) ⇒ Object

Find last position where two fragments differ



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/prosereflect/fragment.rb', line 168

def find_diff_end(other)
  my_nodes = @content.reverse
  other_nodes = other.content.reverse

  i = 0
  end_pos = size

  while i < my_nodes.length && i < other_nodes.length
    my_node = my_nodes[i]
    other_node = other_nodes[i]

    unless my_node == other_node
      return end_pos
    end

    end_pos -= my_node.node_size
    i += 1
  end

  nil
end

#find_diff_start(other) ⇒ Object

Find first position where two fragments differ



152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/prosereflect/fragment.rb', line 152

def find_diff_start(other)
  min_length = [@content.length, other.content.length].min

  pos = 0
  min_length.times do |i|
    return pos if @content[i] != other.content[i]

    pos += @content[i].node_size
  end

  return nil if @content.length == other.content.length

  pos
end

#full_node_callback(node, _pos, _node_end, _from, _to, callback, node_start) ⇒ Object



114
115
116
117
# File 'lib/prosereflect/fragment.rb', line 114

def full_node_callback(node, _pos, _node_end, _from, _to, callback, node_start)
  callback.call(node, node_start)
  recurse_into_node(node, 0, node.content.size, callback, node_start)
end

#hashObject

Hash for use in sets/hashes



201
202
203
# File 'lib/prosereflect/fragment.rb', line 201

def hash
  @content.map(&:to_h).hash
end

#in_range_before_from?(_pos, node_end, from) ⇒ Boolean

Returns:

  • (Boolean)


64
65
66
# File 'lib/prosereflect/fragment.rb', line 64

def in_range_before_from?(_pos, node_end, from)
  node_end <= from
end

#inspectObject



245
246
247
# File 'lib/prosereflect/fragment.rb', line 245

def inspect
  to_s
end

#lengthObject Also known as: count

Number of items



216
217
218
# File 'lib/prosereflect/fragment.rb', line 216

def length
  @content.length
end

#node_fully_in_range?(pos, node_end, from, to) ⇒ Boolean

Returns:

  • (Boolean)


110
111
112
# File 'lib/prosereflect/fragment.rb', line 110

def node_fully_in_range?(pos, node_end, from, to)
  pos >= from && node_end <= to
end

#node_overlaps_from?(pos, node_end, from) ⇒ Boolean

Returns:

  • (Boolean)


123
124
125
# File 'lib/prosereflect/fragment.rb', line 123

def node_overlaps_from?(pos, node_end, from)
  pos < from && node_end > from
end

#nodes_between(from, to, callback = nil, node_start = 0, &blk) ⇒ Object

Iterate over all nodes between positions



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/prosereflect/fragment.rb', line 80

def nodes_between(from, to, callback = nil, node_start = 0, &blk)
  cb = callback || blk
  return unless cb && to > from

  pos = 0

  @content.each do |node|
    node_end = pos + node.node_size
    next unless node_end > from

    dispatch_node_callback(node, pos, node_end, from, to, cb, node_start)
    pos = node_end
    break if pos >= to
  end
end

#overlaps_range?(pos, node_end, from, to) ⇒ Boolean

Returns:

  • (Boolean)


68
69
70
# File 'lib/prosereflect/fragment.rb', line 68

def overlaps_range?(pos, node_end, from, to)
  (pos >= from && node_end <= to) || (pos < from && node_end > from)
end

#partial_node_callback(node, pos, _node_end, from, to, callback, node_start) ⇒ Object



119
120
121
# File 'lib/prosereflect/fragment.rb', line 119

def partial_node_callback(node, pos, _node_end, from, to, callback, node_start)
  recurse_into_node(node, from - pos, [to - pos, node.content.size].min, callback, node_start)
end

#recurse_into_node(node, start_pos, end_pos, callback, node_start) ⇒ Object



127
128
129
130
131
# File 'lib/prosereflect/fragment.rb', line 127

def recurse_into_node(node, start_pos, end_pos, callback, node_start)
  return unless node.respond_to?(:nodes_between)

  node.nodes_between(start_pos, end_pos, callback, node_start)
end

#replace_child(index, replacement) ⇒ Object

Replace child at index



73
74
75
76
77
# File 'lib/prosereflect/fragment.rb', line 73

def replace_child(index, replacement)
  new_content = @content.dup
  new_content[index] = replacement
  Fragment.new(new_content)
end

#sizeObject

Total size of all nodes in this fragment



20
21
22
# File 'lib/prosereflect/fragment.rb', line 20

def size
  @content.sum { |n| n.respond_to?(:node_size) ? n.node_size : n.text_content.length + 1 }
end

#text_between(_from, _to, separator = "", _block_separator = "\n") ⇒ Object

Extract text content between positions



139
140
141
142
143
144
145
146
147
148
149
# File 'lib/prosereflect/fragment.rb', line 139

def text_between(_from, _to, separator = "", _block_separator = "\n")
  result = []
  @content.each do |node|
    if node.respond_to?(:text)
      result << node.text
    elsif node.respond_to?(:text_content)
      result << node.text_content
    end
  end
  result.join(separator)
end

#text_node_callback(node, pos, from, node_start, callback) ⇒ Object



106
107
108
# File 'lib/prosereflect/fragment.rb', line 106

def text_node_callback(node, pos, from, node_start, callback)
  callback.call(node, node_start + (from - pos).clamp(0, node.node_size - 1))
end

#to_aObject

Convert to array



223
224
225
# File 'lib/prosereflect/fragment.rb', line 223

def to_a
  @content.dup
end

#to_sObject



241
242
243
# File 'lib/prosereflect/fragment.rb', line 241

def to_s
  "<Fragment #{@content.length} nodes>"
end