Class: Prosereflect::Transform::Transform
- Inherits:
-
Object
- Object
- Prosereflect::Transform::Transform
- Defined in:
- lib/prosereflect/transform/transform.rb
Overview
A chainable document transformation. Accumulates steps and their mappings.
Defined Under Namespace
Classes: ApplyError, TransformError
Instance Attribute Summary collapse
-
#mapping ⇒ Object
readonly
Returns the value of attribute mapping.
-
#steps ⇒ Object
readonly
Returns the value of attribute steps.
Instance Method Summary collapse
-
#add_mark(from, to, mark) ⇒ Object
Add a mark to all content in range.
-
#add_mark_by_type(from, to, mark_type_name, schema, attrs = nil) ⇒ Object
Add mark using schema.
-
#add_step(step) ⇒ Object
Add a step and track its mapping.
-
#apply ⇒ Object
Apply all accumulated steps to the document Returns self for chaining.
-
#can_apply? ⇒ Boolean
Check if we can step forward.
-
#clone ⇒ Object
Create a new transform with the same document.
-
#delete(from, to) ⇒ Object
Delete content in range.
-
#doc ⇒ Object
Apply and return the transformed document.
-
#empty? ⇒ Boolean
Check if any steps have been applied.
-
#initialize(doc) ⇒ Transform
constructor
A new instance of Transform.
-
#insert(pos, content) ⇒ Object
Insert content at position.
- #inspect ⇒ Object
-
#join(pos, depth = 1) ⇒ Object
Join two nodes at a position.
-
#lift(range, target) ⇒ Object
Lift content out of a wrapper node range: NodeRange representing the content to lift target: depth to lift to.
-
#maps ⇒ Object
Get the mapping for all applied steps.
-
#remove_mark(from, to, mark) ⇒ Object
Remove a mark from all content in range.
-
#remove_mark_by_type(from, to, mark_type_name, schema) ⇒ Object
Remove mark using schema.
-
#replace(from, to, slice = Slice.empty) ⇒ Object
Replace content in range with slice.
-
#replace_with(from, to, *nodes) ⇒ Object
Replace content with specific nodes.
-
#rollback ⇒ Object
Roll back the last step.
-
#set_doc_attribute(attrs) ⇒ Object
Set document attribute.
-
#set_node_attribute(pos, attrs) ⇒ Object
Set attribute on node at position.
-
#size ⇒ Object
Get the number of steps.
-
#split(pos, depth = 1) ⇒ Object
Split a node at a position.
- #to_s ⇒ Object
-
#wrap(range, wrappers) ⇒ Object
Wrap content in nodes range: NodeRange representing the content to wrap wrappers: array of NodeTypeWithAttrs representing wrapper nodes.
Constructor Details
Instance Attribute Details
#mapping ⇒ Object (readonly)
Returns the value of attribute mapping.
18 19 20 |
# File 'lib/prosereflect/transform/transform.rb', line 18 def mapping @mapping end |
#steps ⇒ Object (readonly)
Returns the value of attribute steps.
18 19 20 |
# File 'lib/prosereflect/transform/transform.rb', line 18 def steps @steps end |
Instance Method Details
#add_mark(from, to, mark) ⇒ Object
Add a mark to all content in range
27 28 29 |
# File 'lib/prosereflect/transform/transform.rb', line 27 def add_mark(from, to, mark) add_step(AddMarkStep.new(from, to, mark)) end |
#add_mark_by_type(from, to, mark_type_name, schema, attrs = nil) ⇒ Object
Add mark using schema
136 137 138 139 140 |
# File 'lib/prosereflect/transform/transform.rb', line 136 def add_mark_by_type(from, to, mark_type_name, schema, attrs = nil) mark_type = schema.mark_type(mark_type_name) mark = mark_type.create(attrs) add_mark(from, to, mark) end |
#add_step(step) ⇒ Object
Add a step and track its mapping
98 99 100 101 102 |
# File 'lib/prosereflect/transform/transform.rb', line 98 def add_step(step) @steps << step @mapping.add_map(step.get_map) self end |
#apply ⇒ Object
Apply all accumulated steps to the document Returns self for chaining
70 71 72 73 74 75 76 77 78 |
# File 'lib/prosereflect/transform/transform.rb', line 70 def apply @steps.each do |step| result = step.apply(@doc) raise ApplyError, "Step #{step.class} failed: #{result.failed}" unless result.ok? @doc = result.doc end self end |
#can_apply? ⇒ Boolean
Check if we can step forward
131 132 133 |
# File 'lib/prosereflect/transform/transform.rb', line 131 def can_apply? @steps.all? { |step| step.apply(@doc).ok? } end |
#clone ⇒ Object
Create a new transform with the same document
110 111 112 |
# File 'lib/prosereflect/transform/transform.rb', line 110 def clone Transform.new(@doc) end |
#delete(from, to) ⇒ Object
Delete content in range
42 43 44 |
# File 'lib/prosereflect/transform/transform.rb', line 42 def delete(from, to) add_step(DeleteStep.new(from, to)) end |
#doc ⇒ Object
Apply and return the transformed document
81 82 83 84 85 |
# File 'lib/prosereflect/transform/transform.rb', line 81 def doc # Apply pending steps apply if @steps.any? @doc end |
#empty? ⇒ Boolean
Check if any steps have been applied
88 89 90 |
# File 'lib/prosereflect/transform/transform.rb', line 88 def empty? @steps.empty? end |
#insert(pos, content) ⇒ Object
Insert content at position
37 38 39 |
# File 'lib/prosereflect/transform/transform.rb', line 37 def insert(pos, content) add_step(InsertStep.new(pos, content)) end |
#inspect ⇒ Object
303 304 305 |
# File 'lib/prosereflect/transform/transform.rb', line 303 def inspect to_s end |
#join(pos, depth = 1) ⇒ Object
Join two nodes at a position
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 |
# File 'lib/prosereflect/transform/transform.rb', line 272 def join(pos, depth = 1) resolved = @doc.resolve(pos) d = depth while d.positive? # Check if we can join at this depth parent = resolved.node(d - 1) if d >= 1 idx = resolved.index(d) if parent&.content && idx.positive? && idx < parent.content.size before_node = parent.content[idx - 1] after_node = parent.content[idx] if before_node.type == after_node.type # Join point is at the boundary between before_node and after_node join_from = resolved.start(d) - before_node.node_size join_to = resolved.start(d) + after_node.node_size replace(join_from, join_to, Slice.empty) end end d -= 1 end self end |
#lift(range, target) ⇒ Object
Lift content out of a wrapper node range: NodeRange representing the content to lift target: depth to lift to
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 |
# File 'lib/prosereflect/transform/transform.rb', line 152 def lift(range, target) from_ = range.from to_ = range.to depth = range.depth gap_start = position_before_depth(from_, depth + 1) gap_end = position_after_depth(to_, depth + 1) start = gap_start end_pos = gap_end before = Fragment.empty open_start = 0 d = depth splitting = false while d > target if splitting || node_index_at_depth(from_, d).positive? splitting = true before = Fragment.from(node_at_depth(from_, d).copy(before)) open_start += 1 else start -= 1 end d -= 1 end after = Fragment.empty open_end = 0 d = depth splitting = false while d > target if splitting || position_after_depth(to_, d + 1) < node_end_at_depth(to_, d) splitting = true after = Fragment.from(node_at_depth(to_, d).copy(after)) open_end += 1 else end_pos += 1 end d -= 1 end replace_around_step( start, end_pos, gap_start, gap_end, Slice.new(before.append(after), open_start, open_end), before.size - open_start, true, ) self end |
#maps ⇒ Object
Get the mapping for all applied steps
105 106 107 |
# File 'lib/prosereflect/transform/transform.rb', line 105 def maps @mapping.to_a end |
#remove_mark(from, to, mark) ⇒ Object
Remove a mark from all content in range
32 33 34 |
# File 'lib/prosereflect/transform/transform.rb', line 32 def remove_mark(from, to, mark) add_step(RemoveMarkStep.new(from, to, mark)) end |
#remove_mark_by_type(from, to, mark_type_name, schema) ⇒ Object
Remove mark using schema
143 144 145 146 147 |
# File 'lib/prosereflect/transform/transform.rb', line 143 def remove_mark_by_type(from, to, mark_type_name, schema) mark_type = schema.mark_type(mark_type_name) mark = mark_type.create remove_mark(from, to, mark) end |
#replace(from, to, slice = Slice.empty) ⇒ Object
Replace content in range with slice
47 48 49 |
# File 'lib/prosereflect/transform/transform.rb', line 47 def replace(from, to, slice = Slice.empty) add_step(ReplaceStep.new(from, to, slice)) end |
#replace_with(from, to, *nodes) ⇒ Object
Replace content with specific nodes
52 53 54 55 56 |
# File 'lib/prosereflect/transform/transform.rb', line 52 def replace_with(from, to, *nodes) content = Fragment.new(nodes.flatten) slice = Slice.new(content) add_step(ReplaceStep.new(from, to, slice)) end |
#rollback ⇒ Object
Roll back the last step
115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/prosereflect/transform/transform.rb', line 115 def rollback return self if @steps.empty? step = @steps.pop @mapping = Mapping.new(maps: @mapping.to_a[0...-1]) inverted = step.invert(@doc) result = inverted.apply(@doc) if result.ok? @doc = result.doc end self end |
#set_doc_attribute(attrs) ⇒ Object
Set document attribute
64 65 66 |
# File 'lib/prosereflect/transform/transform.rb', line 64 def set_doc_attribute(attrs) add_step(DocAttrStep.new(attrs)) end |
#set_node_attribute(pos, attrs) ⇒ Object
Set attribute on node at position
59 60 61 |
# File 'lib/prosereflect/transform/transform.rb', line 59 def set_node_attribute(pos, attrs) add_step(AttrStep.new(pos, attrs)) end |
#size ⇒ Object
Get the number of steps
93 94 95 |
# File 'lib/prosereflect/transform/transform.rb', line 93 def size @steps.length end |
#split(pos, depth = 1) ⇒ Object
Split a node at a position
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 |
# File 'lib/prosereflect/transform/transform.rb', line 238 def split(pos, depth = 1) resolved = @doc.resolve(pos) before = Fragment.empty open_start = 0 open_end = 0 d = depth while d.positive? node = resolved.node(d) if d == depth before = Fragment.from(node.copy(Fragment.empty)) open_start = node.is_a?(Prosereflect::Node) && node.content&.size.to_i.positive? ? 1 : 0 open_end = 0 else before = Fragment.from(node.copy(before)) open_start += 1 end d -= 1 end step = ReplaceAroundStep.new( pos, pos, pos, pos, Slice.new(before, open_start, open_end), open_start, structure: false, ) add_step(step) self end |
#to_s ⇒ Object
299 300 301 |
# File 'lib/prosereflect/transform/transform.rb', line 299 def to_s "<Transform steps=#{@steps.length}>" end |
#wrap(range, wrappers) ⇒ Object
Wrap content in nodes range: NodeRange representing the content to wrap wrappers: array of NodeTypeWithAttrs representing wrapper nodes
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 |
# File 'lib/prosereflect/transform/transform.rb', line 207 def wrap(range, wrappers) content = Fragment.empty i = wrappers.length - 1 while i >= 0 if content.size.positive? match = wrappers[i].type.content_match.match_fragment(content) unless match&.valid_end raise TransformError, "Wrapper type given to Transform.wrap does not form valid content of its parent wrapper" end end content = Fragment.from( wrappers[i].type.create(wrappers[i].attrs, content), ) i -= 1 end start = range.start end_pos = range.end replace_around_step( start, end_pos, start, end_pos, Slice.new(content, 0, 0), wrappers.length, true, ) self end |