def merge(self, other: "ReplaceStep"): if not (isinstance(other, ReplaceStep) or other.structure != self.structure): return None if (self.from_ + self.slice.size == other.from_ and not self.slice.open_end and not other.slice.open_start): if self.slice.size + other.slice.size == 0: slice = Slice.empty else: slice = Slice( self.slice.content.append(other.slice.content), self.slice.open_start, other.slice.open_end, ) return ReplaceStep(self.from_, self.to + (other.to - other.from_), slice, self.structure) elif (other.to == self.from_ and not self.slice.open_start and not other.slice.open_end): if self.slice.size + other.slice.size == 0: slice = Slice.empty else: slice = Slice( other.slice.content.append(self.slice.content), other.slice.open_start, self.slice.open_end, ) return ReplaceStep(other.from_, self.to, slice, self.structure) return None
def test_replace(doc, from_, to, content, open_start, open_end, result): if content: slice = Slice(content.content, open_start, open_end) else: slice = Slice.empty tr = Transform(doc).replace(from_, to, slice) assert tr.doc.eq(result)
def replace_range_with(self, from_, to, node): if (not node.is_inline and from_ == to and self.doc.resolve(from_).parent.content.size): point = insert_point(self.doc, from_, node.type) if point is not None: from_ = to = point return self.replace_range(from_, to, Slice(Fragment.from_(node), 0, 0))
def _make_step(from_, to, val): if val == "+em": return AddMarkStep(from_, to, schema.marks["em"].create) elif val == "-em": return RemoveMarkStep(from_, to, schema.marks["em"].create) return ReplaceStep( from_, to, Slice.empty if val is None else Slice( Fragment.from_(schema.text(val), 0, 0)), )
def apply(self, doc): old_slice = doc.slice(self.from_, self.to) def iteratee(node, *args): return node.mark(self.mark.remove_from_set(node.marks)) slice = Slice( map_fragment(old_slice.content, iteratee), old_slice.open_start, old_slice.open_end, ) return StepResult.from_replace(doc, self.from_, self.to, slice)
def wrap(self, range_, wrappers): content = Fragment.empty i = len(wrappers) - 1 while i >= 0: content = Fragment.from_(wrappers[i]["type"].create( wrappers[i].get("attrs"), content)) i -= 1 start = range_.start end = range_.end return self.step( ReplaceAroundStep(start, end, start, end, Slice(content, 0, 0), len(wrappers), True))
def from_json(schema, json_data): if isinstance(json_data, str): import json json_data = json.loads(json_data) if not isinstance(json_data["from"], int) or not isinstance( json_data["to"], int): raise ValueError("Invlid input for ReplaceStep.from_json") return ReplaceStep( json_data["from"], json_data["to"], Slice.from_json(schema, json_data.get("slice")), bool(json_data.get("structure")), )
def apply(self, doc): old_slice = doc.slice(self.from_, self.to) from__ = doc.resolve(self.from_) parent = from__.node(from__.shared_depth(self.to)) def iteratee(node, parent, *args): if not parent.type.allows_mark_type(self.mark.type): return node return node.mark(self.mark.add_to_set(node.marks)) slice = Slice( map_fragment(old_slice.content, iteratee, parent), old_slice.open_start, old_slice.open_end, ) return StepResult.from_replace(doc, self.from_, self.to, slice)
def lift(self, range_, target): from__ = range_.from_ to_ = range_.to depth = range_.depth gap_start = from__.before(depth + 1) gap_end = to_.after(depth + 1) start = gap_start end = gap_end before = Fragment.empty open_start = 0 d = depth splitting = False while d > target: if splitting or from__.index(d) > 0: splitting = True before = Fragment.from_(from__.node(d).copy(before)) open_start += 1 else: start -= 1 d -= 1 after = Fragment.empty open_end = 0 d = depth splitting = False while d > target: if splitting or to_.after(d + 1) < to_.end(d): splitting = True after = Fragment.from_(to_.node(d).copy(after)) open_end += 1 else: end += 1 d -= 1 return self.step( ReplaceAroundStep( start, end, gap_start, gap_end, Slice(before.append(after), open_start, open_end), before.size - open_start, True, ))
def set_node_markup(self, pos, type, attrs, marks=None): node = self.doc.node_at(pos) if not node: raise ValueError("No node at given position") if not type: type = node.type new_node = type.create(attrs, None, marks or node.marks) if node.is_leaf: return self.replace_with(pos, pos + node.node_size, new_node) if not type.valid_content(node.content): raise ValueError(f"Invalid content for node type {type.name}") return self.step( ReplaceAroundStep( pos, pos + node.node_size, pos + 1, pos + node.node_size - 1, Slice(Fragment.from_(new_node), 0, 0), 1, True, ))
def split(self, pos, depth=None, types_after=None): if depth is None: depth = 1 pos_ = self.doc.resolve(pos) before = Fragment.empty after = Fragment.empty d = pos_.depth e = pos_.depth - depth i = depth - 1 while d > e: before = Fragment.from_(pos_.node(d).copy(before)) type_after = None if types_after and len(types_after) > i: type_after = types_after[i] after = Fragment.from_( type_after["type"].create(type_after.get("attrs"), after) if type_after else pos_.node(d).copy(after)) d -= 1 i -= 1 return self.step( ReplaceStep(pos, pos, Slice(before.append(after), depth, depth), True))
def iteratee(node: "Node", pos, *args): if (node.is_text_block and not node.has_markup(type, attrs) and can_change_type( self.doc, self.mapping.slice(map_from).map(pos), type)): self.clear_incompatible( self.mapping.slice(map_from).map(pos, 1), type) mapping = self.mapping.slice(map_from) start_m = mapping.map(pos, 1) end_m = mapping.map(pos + node.node_size, 1) self.step( ReplaceAroundStep( start_m, end_m, start_m + 1, end_m - 1, Slice( Fragment.from_(type.create(attrs, None, node.marks)), 0, 0), 1, True, )) return False
def clear_incompatible(self, pos, parent_type, match=None): if match is None: match = parent_type.content_match node = self.doc.node_at(pos) del_steps = [] cur = pos + 1 for i in range(node.child_count): child = node.child(i) end = cur + child.node_size allowed = match.match_type(child.type, child.attrs) if not allowed: del_steps.append(ReplaceStep(cur, end, Slice.empty)) else: match = allowed for j in range(len(child.marks)): if not parent_type.allows_mark_type(child.marks[j].type): self.step(RemoveMarkStep(cur, end, child.marks[j])) cur = end if not match.valid_end: fill = match.fill_before(Fragment.empty, True) self.replace(cur, cur, Slice(fill, 0, 0)) for item in reversed(del_steps): self.step(item) return self
def replace_range(self, from_, to, slice): if not slice.size: return self.delete_range(from_, to) from__ = self.doc.resolve(from_) to_ = self.doc.resolve(to) if fits_trivially(from__, to_, slice): return self.step(ReplaceStep(from_, to, slice)) target_depths = covered_depths(from__, self.doc.resolve(to)) if target_depths and target_depths[-1] == 0: target_depths.pop() preferred_target = -(from__.depth + 1) target_depths.insert(0, preferred_target) d = from__.depth pos = from__.pos - 1 while d > 0: spec = from__.node(d).type.spec if spec.get("defining") or spec.get("isolating"): break if d in target_depths: preferred_target = d elif from__.before(d) == pos: target_depths.insert(1, -d) d -= 1 pos -= 1 preferred_target_index = target_depths.index(preferred_target) left_nodes = [] preferred_depth = slice.open_start content = slice.content i = 0 while True: node = content.first_child left_nodes.append(node) if i == slice.open_start: break content = node.content i += 1 if (preferred_depth > 0 and left_nodes[preferred_depth - 1].type.spec.get("defining") and from__.node(preferred_target_index).type.name != left_nodes[preferred_depth - 1].type.name): preferred_depth -= 1 elif (preferred_depth >= 2 and left_nodes[preferred_depth - 1].is_text_block and left_nodes[preferred_depth - 2].type.spec.get("defining") and from__.node(preferred_target_index).type.name != left_nodes[preferred_depth - 2].type.name): preferred_depth -= 2 for j in range(slice.open_start, -1, -1): open_depth = (j + preferred_depth + 1) % (slice.open_start + 1) if len(left_nodes) > open_depth: insert = left_nodes[open_depth] else: continue for i in range(len(target_depths)): target_depth = target_depths[(i + preferred_target_index) % len(target_depths)] expand = True if target_depth < 0: expand = False target_depth = -target_depth parent = from__.node(target_depth - 1) index = from__.index(target_depth - 1) if parent.can_replace_with(index, index, insert.type, insert.marks): return self.replace( from__.before(target_depth), to_.after(target_depth) if expand else to, Slice( close_fragment(slice.content, 0, slice.open_start, open_depth, None), open_depth, slice.open_end, ), ) return self.replace(from_, to, slice)
def replace_with(self, from_, to, content): return self.replace(from_, to, Slice(Fragment.from_(content), 0, 0))
def place_content(self, fragment, open_start, open_end, pass_, parent=None): i = 0 while i < fragment.child_count: child = fragment.child(i) placed = False last = i == (fragment.child_count - 1) d = len(self.open) - 1 while d >= 0: open = self.open[d] wrap = None if pass_ > 1: wrap = open["match"].find_wrapping(child.type) if wrap and not (parent and len(wrap) and wrap[-1] == parent.type): while len(self.open) - 1 > d: self.close_node() w = 0 while w < len(wrap): open["match"] = open["match"].match_type(wrap[w]) d += 1 open = { "parent": wrap[w].create(), "match": wrap[w].content_match, "content": Fragment.empty, "wrapper": True, "open_end": 0, "depth": d + w, } self.open.append(open) w += 1 match = open["match"].match_type(child.type) if not match: fill = open["match"].fill_before(Fragment.from_(child)) if fill: for j in range(fill.child_count): ch = fill.child(j) self.add_node(open, ch, 0) match = open["match"].match_fragment(ch) elif parent and open["match"].match_type(parent.type): break else: d -= 1 continue while len(self.open) - 1 > d: self.close_node() child = child.mark(open["parent"].type.allowed_marks(child.marks)) if open_start: child = close_node_start(child, open_start, open_end if last else 0) open_start = 0 self.add_node(open, child, open_end if last else 0) open["match"] = match if last: open_end = 0 placed = True break if not placed: break i += 1 if len(self.open) > 1 and ( i > 0 and i == fragment.child_count or parent and self.open[-1]["parent"].type == parent.type ): self.close_node() return Slice(fragment.cut_by_index(i), open_start, open_end)
def fit_left(from__, placed): inner_res = fit_left_innter(from__, 0, placed, False) content = inner_res["content"] open_end = inner_res["open_end"] return Slice(content, from__.depth, open_end or 0)
doc(p("<a>hi<b>")), doc(blockquote(p("hix"))), ), ( doc(p("x<a>hi"), blockquote(p("yy"), "<b>"), p("c")), doc(p("<a>hi<b>")), doc(p("xhi"), blockquote(p()), p("c")), ), (doc(p("<a>x")), doc(blockquote(p("hi"), "<a>"), p("b<b>")), doc(p(), p("bx"))), ( doc(p("<a>x")), doc(p("b<a>"), blockquote("<b>", p("hi"))), doc(p(), blockquote(p()), p("x")), ), (p("<a>x"), Slice(Fragment.from_([blockquote(), hr()]), 0, 0), p("x")), ( doc(p("foo"), "<a>", p("bar<b>")), ol(li(p("<a>a")), li(p("b<b>"))), doc(p("foo"), p("a"), ol(li(p("b")))), ), ( doc(ul(li(p("ab<a>cd")), li(p("ef<b>gh")))), doc(ul(li(p("ABCD")), li(p("EFGH")))).slice(5, 13, True), doc(ul(li(p("abCD")), li(p("EFgh")))), ), ( doc(ul(li(p("foo")), "<a>", li(p("bar")))), ul(li(p("a<a>bc")), li(p("de<b>f"))), doc(ul(li(p("foo")), li(p("bc")), li(p("de")), li(p("bar")))), ),
def normalize_slice(content, open_start, open_end): while open_start > 0 and open_end > 0 and content.child_count == 1: content = content.first_child.content open_start -= 1 open_end -= 1 return Slice(content, open_start, open_end)