Class: Prosereflect::Node
- Inherits:
-
Lutaml::Model::Serializable
- Object
- Lutaml::Model::Serializable
- Prosereflect::Node
- Defined in:
- lib/prosereflect/resolved_pos.rb,
lib/prosereflect/node.rb
Overview
Extension to Node for position resolution
Direct Known Subclasses
Blockquote, BulletList, CodeBlock, CodeBlockWrapper, Document, HardBreak, Heading, HorizontalRule, Image, ListItem, OrderedList, Paragraph, Table, TableCell, TableRow, Text, User
Constant Summary collapse
- PM_TYPE =
"node"
Class Method Summary collapse
Instance Method Summary collapse
-
#add_child(node) ⇒ Object
Add a child node to this node’s content.
-
#copy(new_content = nil) ⇒ Object
Create a copy of this node with different content.
-
#cut(from = 0, to = nil) ⇒ Object
Return a copy of this node with content restricted to the given range.
-
#descendants(&block) ⇒ Object
Iterate over all descendant nodes.
-
#eq?(other) ⇒ Boolean
Check structural equality with another node.
- #find_all(node_type) ⇒ Object
- #find_children(node_type) ⇒ Object
- #find_first(node_type) ⇒ Object
-
#initialize(data = nil, attrs = nil) ⇒ Node
constructor
A new instance of Node.
- #marks ⇒ Object
- #marks=(value) ⇒ Object
-
#node(depth) ⇒ Object
Get the node at a given depth in the path.
-
#node_size ⇒ Object
Size of this node in the document tree.
-
#nodes_between(from, to, callback = nil, node_start = 0, &block) ⇒ Object
Iterate over all nodes between two positions in this node.
- #parse_content(content_data) ⇒ Object
- #process_attrs_data(attrs_data) ⇒ Object
- #raw_marks ⇒ Object
-
#resolve(pos) ⇒ Object
Resolve a position to a ResolvedPos.
-
#text? ⇒ Boolean
Whether this node represents a text node.
- #text_content ⇒ Object
-
#to_h ⇒ Object
(also: #to_hash)
Convert to hash for serialization.
-
#to_yaml(*args) ⇒ Object
Ensures YAML serialization outputs plain data instead of a Ruby object.
Constructor Details
#initialize(data = nil, attrs = nil) ⇒ Node
Returns a new instance of Node.
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/prosereflect/node.rb', line 19 def initialize(data = nil, attrs = nil) if data.is_a?(String) super(type: data, attrs: attrs, content: []) elsif data.is_a?(Hash) # Handle marks in a special way to preserve expected behavior in tests if data[:marks] || data["marks"] marks_data = data[:marks] || data["marks"] data = data.dup data.delete("marks") data.delete(:marks) super(data) self.marks = marks_data else # Handle attrs properly if data[:attrs] || data["attrs"] data = data.dup data[:attrs] = process_attrs_data(data[:attrs] || data["attrs"]) end super(data) end else super() end end |
Class Method Details
.create(type = nil, attrs = nil) ⇒ Object
52 53 54 55 56 |
# File 'lib/prosereflect/node.rb', line 52 def self.create(type = nil, attrs = nil) new(type || self::PM_TYPE, attrs) rescue NameError new(type || "node", attrs) end |
Instance Method Details
#add_child(node) ⇒ Object
Add a child node to this node’s content
167 168 169 170 171 |
# File 'lib/prosereflect/node.rb', line 167 def add_child(node) self.content ||= [] content << node node end |
#copy(new_content = nil) ⇒ Object
Create a copy of this node with different content.
278 279 280 281 282 283 284 285 286 287 288 289 290 291 |
# File 'lib/prosereflect/node.rb', line 278 def copy(new_content = nil) new_node = self.class.new(type: type, attrs: attrs, marks: raw_marks) case new_content when nil # no content when Array new_node.content = new_content when Fragment new_node.content = new_content.to_a else new_node.content = [new_content] end new_node end |
#cut(from = 0, to = nil) ⇒ Object
Return a copy of this node with content restricted to the given range. Positions are relative to the start of this node’s content.
226 227 228 229 230 231 232 233 234 235 236 |
# File 'lib/prosereflect/node.rb', line 226 def cut(from = 0, to = nil) to ||= node_size return self if from.zero? && to == node_size if text? # Text nodes override this self else copy(cut_content(from, to)) end end |
#descendants(&block) ⇒ Object
Iterate over all descendant nodes.
266 267 268 |
# File 'lib/prosereflect/node.rb', line 266 def descendants(&block) nodes_between(0, node_size - 1, &block) end |
#eq?(other) ⇒ Boolean
Check structural equality with another node.
271 272 273 274 275 |
# File 'lib/prosereflect/node.rb', line 271 def eq?(other) return false unless other.is_a?(Node) type == other.type && to_h == other.to_h end |
#find_all(node_type) ⇒ Object
185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/prosereflect/node.rb', line 185 def find_all(node_type) results = [] results << self if type == node_type content&.each do |child| child_results = child.find_all(node_type) results.concat(child_results) if child_results end results end |
#find_children(node_type) ⇒ Object
197 198 199 200 201 |
# File 'lib/prosereflect/node.rb', line 197 def find_children(node_type) return [] unless content content.grep(node_type) end |
#find_first(node_type) ⇒ Object
173 174 175 176 177 178 179 180 181 182 183 |
# File 'lib/prosereflect/node.rb', line 173 def find_first(node_type) return self if type == node_type return nil unless content content.each do |child| result = child.find_first(node_type) return result if result end nil end |
#marks ⇒ Object
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/prosereflect/node.rb', line 105 def marks return nil if @marks.nil? return [] if @marks.empty? @marks.map do |mark| if mark.is_a?(Hash) mark elsif mark.respond_to?(:to_h) mark.to_h elsif mark.respond_to?(:type) { "type" => mark.type.to_s } else raise ArgumentError, "Invalid mark type: #{mark.class}" end end end |
#marks=(value) ⇒ Object
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 |
# File 'lib/prosereflect/node.rb', line 126 def marks=(value) if value.nil? @marks = nil elsif value.is_a?(Array) && value.empty? @marks = [] elsif value.is_a?(Array) @marks = value.map do |v| if v.is_a?(Hash) type = v["type"] || v[:type] attrs = v["attrs"] || v[:attrs] begin mark_class = Prosereflect::Mark.const_get(type.to_s.capitalize) mark_class.new(attrs: attrs) rescue NameError Mark::Base.new(type: type, attrs: attrs) end elsif v.is_a?(Mark::Base) v elsif v.respond_to?(:type) begin mark_class = Prosereflect::Mark.const_get(v.type.to_s.capitalize) mark_class.new(attrs: v.attrs) rescue NameError Mark::Base.new(type: v.type, attrs: v.attrs) end else raise ArgumentError, "Invalid mark type: #{v.class}" end end else super end end |
#node(depth) ⇒ Object
Get the node at a given depth in the path
307 308 309 |
# File 'lib/prosereflect/node.rb', line 307 def node(depth) @path[depth * 2] end |
#node_size ⇒ Object
Size of this node in the document tree. For non-text nodes: 1 (opening token) + sum of children’s node_size. For text nodes: overridden to text.length + 1.
212 213 214 215 216 |
# File 'lib/prosereflect/node.rb', line 212 def node_size size = 1 content&.each { |child| size += child.node_size } size end |
#nodes_between(from, to, callback = nil, node_start = 0, &block) ⇒ Object
Iterate over all nodes between two positions in this node. Accepts a block or a callable as the third positional argument.
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 |
# File 'lib/prosereflect/node.rb', line 240 def nodes_between(from, to, callback = nil, node_start = 0, &block) cb = callback || block return unless cb && to > from && content pos = 0 content.each_with_index do |child, i| break if pos >= to child_end = pos + child.node_size next unless child_end > from child_start = node_start + pos + 1 if cb.call(child, child_start, i) != false && child.content && child.content.any? child.nodes_between( [0, from - pos - 1].max, [child.content ? child.content.size : 0, to - pos - 1].min, cb, child_start, ) end pos = child_end end end |
#parse_content(content_data) ⇒ Object
160 161 162 163 164 |
# File 'lib/prosereflect/node.rb', line 160 def parse_content(content_data) return [] unless content_data content_data.map { |item| Parser.parse(item) } end |
#process_attrs_data(attrs_data) ⇒ Object
44 45 46 47 48 49 50 |
# File 'lib/prosereflect/node.rb', line 44 def process_attrs_data(attrs_data) if attrs_data.is_a?(Hash) attrs_data.transform_keys(&:to_s) else attrs_data end end |
#raw_marks ⇒ Object
122 123 124 |
# File 'lib/prosereflect/node.rb', line 122 def raw_marks @marks end |
#resolve(pos) ⇒ Object
Resolve a position to a ResolvedPos
299 300 301 302 303 304 |
# File 'lib/prosereflect/node.rb', line 299 def resolve(pos) path = [] build_path_for_pos(pos, path) depth = [(path.length / 3) - 1, 0].max ResolvedPos.new(pos, path, depth) end |
#text? ⇒ Boolean
Whether this node represents a text node. Overridden to true in Text class.
220 221 222 |
# File 'lib/prosereflect/node.rb', line 220 def text? false end |
#text_content ⇒ Object
203 204 205 206 207 |
# File 'lib/prosereflect/node.rb', line 203 def text_content return "" unless content content.map(&:text_content).join end |
#to_h ⇒ Object Also known as: to_hash
Convert to hash for serialization
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 |
# File 'lib/prosereflect/node.rb', line 59 def to_h result = { "type" => type } if attrs && !attrs.empty? if attrs.is_a?(Hash) result["attrs"] = process_node_attributes(attrs, type) elsif attrs.is_a?(Array) && attrs.all? do |attr| attr.respond_to?(:to_h) end # Convert array of attribute objects to a hash attrs_array = attrs.map do |attr| attr.is_a?(Prosereflect::Attribute::Base) ? attr.to_h : attr end result["attrs"] = attrs_array unless attrs_array.empty? end end if marks && !marks.empty? result["marks"] = marks.map do |mark| if mark.is_a?(Hash) mark elsif mark.respond_to?(:to_h) mark.to_h elsif mark.respond_to?(:type) { "type" => mark.type.to_s } else raise ArgumentError, "Invalid mark type: #{mark.class}" end end end if content && !content.empty? result["content"] = if content.is_a?(Array) content.map do |item| item.respond_to?(:to_h) ? item.to_h : item end else [content] end end result end |
#to_yaml(*args) ⇒ Object
Ensures YAML serialization outputs plain data instead of a Ruby object
294 295 296 |
# File 'lib/prosereflect/node.rb', line 294 def to_yaml(*args) to_h.to_yaml(*args) end |