def test_move_node(self): # Move 1 down action = actions.MoveNode("/document/node[1]", "/document", 1) expected = "[move, /document/node[1], /document, 1]" self._format_test(action, expected) # Move 2 up (same result, different diff) action = actions.MoveNode("/document/node[2]", "/document", 0) expected = "[move, /document/node[2], /document, 0]" self._format_test(action, expected)
def test_move_node(self): # Move 1 down action = actions.MoveNode('/document/node[1]', '/document', 1) expected = '[move, /document/node[1], /document, 1]' self._format_test(action, expected) # Move 2 up (same result, different diff) action = actions.MoveNode('/document/node[2]', '/document', 0) expected = '[move, /document/node[2], /document, 0]' self._format_test(action, expected)
def test_move_node(self): # Move 1 down left = '<document><node id="1" /><node id="2" /></document>' action = actions.MoveNode("/document/node[1]", "/document", 1) expected = (START + ' id="1" diff:delete=""/><node id="2"/><node ' 'id="1" diff:insert=""/></document>') self._format_test(left, action, expected) # Move 2 up (same result, different diff) left = '<document><node id="1" /><node id="2" /></document>' action = actions.MoveNode("/document/node[2]", "/document", 0) expected = (START + ' id="2" diff:insert=""/><node id="1"/><node ' 'id="2" diff:delete=""/></document>') self._format_test(left, action, expected)
def test_move_node(self): # Move 1 down left = u'<document><node id="1" /><node id="2" /></document>' action = actions.MoveNode('/document/node[1]', '/document', 1) expected = START + u' id="1" diff:delete=""/><node id="2"/><node '\ u'id="1" diff:insert=""/></document>' self._format_test(left, action, expected) # Move 2 up (same result, different diff) left = u'<document><node id="1" /><node id="2" /></document>' action = actions.MoveNode('/document/node[2]', '/document', 0) expected = START + u' id="2" diff:insert=""/><node id="1"/><node '\ u'id="2" diff:delete=""/></document>' self._format_test(left, action, expected)
def test_rename_node(self): # Move 1 down action = actions.RenameNode("/document/node[1]", "newtag") expected = "[rename, /document/node[1], newtag]" self._format_test(action, expected) # Move 2 up (same result, different diff) action = actions.MoveNode("/document/node[2]", "/document", 0) expected = "[move-first, /document/node[2], /document]" self._format_test(action, expected)
def test_rename_node(self): # Move 1 down action = actions.RenameNode('/document/node[1]', 'newtag') expected = '[rename, /document/node[1], newtag]' self._format_test(action, expected) # Move 2 up (same result, different diff) action = actions.MoveNode('/document/node[2]', '/document', 0) expected = '[move-first, /document/node[2], /document]' self._format_test(action, expected)
def align_children(self, left, right): lchildren = [ c for c in left.getchildren() if (id(c) in self._l2rmap and self._l2rmap[id(c)].getparent() is right) ] rchildren = [ c for c in right.getchildren() if (id(c) in self._r2lmap and self._r2lmap[id(c)].getparent() is left) ] if not lchildren or not rchildren: # Nothing to align return lcs = utils.longest_common_subsequence( lchildren, rchildren, lambda x, y: self._l2rmap[id(x)] is y) for x, y in lcs: # Mark these as in order self._inorder.add(lchildren[x]) self._inorder.add(rchildren[y]) # Go over those children that are not in order: for lchild in lchildren: if lchild in self._inorder: # Already aligned continue rchild = self._l2rmap[id(lchild)] right_pos = self.find_pos(rchild) rtarget = rchild.getparent() ltarget = self._r2lmap[id(rtarget)] yield actions.MoveNode(utils.getpath(lchild), utils.getpath(ltarget), right_pos) # Do the actual move: left.remove(lchild) ltarget.insert(right_pos, lchild) # Mark the nodes as in order self._inorder.add(lchild) self._inorder.add(rchild)
def diff(self, left=None, right=None): # Make sure the matching is done first, diff() needs the l2r/r2l maps. if not self._matches: self.match(left, right) # The paper talks about the five phases, and then does four of them # in one phase, in a different order that described. This # implementation in turn differs in order yet again. ltree = self.left.getroottree() for rnode in utils.breadth_first_traverse(self.right): # (a) rparent = rnode.getparent() ltarget = self._r2lmap.get(id(rparent)) # (b) Insert if id(rnode) not in self._r2lmap: # (i) pos = self.find_pos(rnode) # (ii) if rnode.tag is etree.Comment: yield actions.InsertComment(utils.getpath(ltarget, ltree), pos, rnode.text) lnode = etree.Comment(rnode.text) else: yield actions.InsertNode(utils.getpath(ltarget, ltree), rnode.tag, pos) lnode = ltarget.makeelement(rnode.tag) # (iii) self.append_match(lnode, rnode, 1.0) ltarget.insert(pos, lnode) self._inorder.add(lnode) self._inorder.add(rnode) # And then we update attributes. This is different from the # paper, because the paper assumes nodes only has labels and # values. Nodes also has texts, we do them later. for action in self.update_node_attr(lnode, rnode): yield action # (c) else: # Normally there is a check that rnode isn't a root, # but that's perhaps only because comparing valueless # roots is pointless, but in an elementtree we have no such # thing as a valueless root anyway. # (i) lnode = self._r2lmap[id(rnode)] # (iii) Move lparent = lnode.getparent() if ltarget is not lparent: pos = self.find_pos(rnode) yield actions.MoveNode(utils.getpath(lnode, ltree), utils.getpath(ltarget, ltree), pos) # Move the node from current parent to target lparent.remove(lnode) ltarget.insert(pos, lnode) self._inorder.add(lnode) self._inorder.add(rnode) # Rename for action in self.update_node_tag(lnode, rnode): yield action # (ii) Update # XXX If they are exactly equal, we can skip this, # maybe store match results in a cache? for action in self.update_node_attr(lnode, rnode): yield action # (d) Align for action in self.align_children(lnode, rnode): yield action # And lastly, we update all node texts. We do this after # aligning children, because when you generate an XML diff # from this, that XML diff update generates more children, # confusing later inserts or deletes. lnode = self._r2lmap[id(rnode)] for action in self.update_node_text(lnode, rnode): yield action for lnode in utils.reverse_post_order_traverse(self.left): if id(lnode) not in self._l2rmap: # No match yield actions.DeleteNode(utils.getpath(lnode, ltree)) lnode.getparent().remove(lnode)
def _handle_move(self, node, target, position): return actions.MoveNode(node, target, int(position))