Commit cc1728c8 authored by Nicolas Delaby's avatar Nicolas Delaby

Add algorythm to detect nodes whose position has changed

parent 27a9bcf3
...@@ -276,6 +276,50 @@ class ERP5Diff: ...@@ -276,6 +276,50 @@ class ERP5Diff:
child_element.text = element.text child_element.text = element.text
last_append_element = append_element last_append_element = append_element
def _xupdateMoveElements(self, misplaced_node_dict, path, nsmap=None):
"""
"""
root = self._getResultRoot()
to_remove_node_list = []
for element_list in misplaced_node_dict.values():
for element_tuple in element_list:
to_remove_node_list.append(element_tuple[0])
child_path_list = self._makeRelativePathList(to_remove_node_list)
for child_path in child_path_list:
to_remove_path = self._concatPath(path, child_path)
self._xupdateRemoveElement(to_remove_path)
for previous, element_tuple_list in misplaced_node_dict.items():
if previous is None:
append_element = etree.SubElement(root, '{%s}append' % self._ns, nsmap=nsmap)
append_element.attrib['child'] = 'first()'
else:
append_element = etree.SubElement(root, '{%s}insert-after' % self._ns, nsmap=nsmap)
path_list = self._makeRelativePathList([previous])
preceding_sibling_path = self._concatPath(path, path_list[0])
append_element.attrib['select'] = preceding_sibling_path
for element_tuple in element_tuple_list:
element = element_tuple[1]
child_element = etree.SubElement(append_element, '{%s}element' % self._ns, nsmap=root.nsmap)
child_element.attrib['name'] = element.xpath('name()')
namespace_uri = element.xpath('namespace-uri()')
if namespace_uri:
child_element.attrib['namespace'] = namespace_uri
attr_map = element.attrib
for name, value in attr_map.items():
attr_element = etree.SubElement(child_element, '{%s}attribute' % self._ns, nsmap=child_element.nsmap)
name, namespace_uri = self._getQName(element, name)
attr_element.attrib['name'] = name
if namespace_uri:
attr_element.attrib['namespace'] = namespace_uri
attr_element.text = value
for child in element:
clone_node = deepcopy(child)
child_element.append(clone_node)
if self._hasChildren(child_element) and element.text is not None:
child_element[-1].tail = element.text
else:
child_element.text = element.text
def _testElements(self, element1, element2): def _testElements(self, element1, element2):
""" """
Test if two given elements are matching. Matching does not mean that they are identical. Test if two given elements are matching. Matching does not mean that they are identical.
...@@ -406,17 +450,31 @@ class ERP5Diff: ...@@ -406,17 +450,31 @@ class ERP5Diff:
def _removeStrictEqualsSubNodeList(self, old_list, new_list): def _removeStrictEqualsSubNodeList(self, old_list, new_list):
"""Remove inside list all elements which are similar """Remove inside list all elements which are similar
by using c14n serialisation by using c14n serialisation
This script returns the same list of nodes whithout twins from other list
and a dictionary with nodes whose position has changed.
misplaced_node_dict :
key = anchor_node (node from which the moving node_list will be append)
value = list of tuple:
-old_element (to remove)
-new_element (to insert)
""" """
old_candidate_list = old_list[:] old_candidate_list = old_list[:]
new_candidate_list = new_list[:] new_candidate_list = new_list[:]
for old_element in old_list: misplaced_node_dict = {}
misplaced_node_dict_after = {}
misplaced_node_dict_before = {}
old_new_index_mapping = {}
for old_index, old_element in enumerate(old_list):
if old_element not in old_candidate_list:
continue
old_tree = deepcopy(old_element).getroottree() old_tree = deepcopy(old_element).getroottree()
old_c14n = StringIO() old_c14n = StringIO()
old_tree.write_c14n(old_c14n) old_tree.write_c14n(old_c14n)
old_c14n.seek(0) old_c14n.seek(0)
for new_element in new_list: for new_element in new_list:
new_index = new_list.index(new_element)
if new_element not in new_candidate_list: if new_element not in new_candidate_list:
continue continue
new_tree = deepcopy(new_element).getroottree() new_tree = deepcopy(new_element).getroottree()
new_c14n = StringIO() new_c14n = StringIO()
new_tree.write_c14n(new_c14n) new_tree.write_c14n(new_c14n)
...@@ -430,12 +488,56 @@ class ERP5Diff: ...@@ -430,12 +488,56 @@ class ERP5Diff:
file_equality = False file_equality = False
old_c14n.seek(0) old_c14n.seek(0)
if file_equality: if file_equality:
index_key_on_new_tree = new_element.getparent().index(new_element)
old_new_index_mapping[index_key_on_new_tree] = old_element
new_start = new_index + 1
if new_element in new_candidate_list: if new_element in new_candidate_list:
new_candidate_list.remove(new_element) new_candidate_list.remove(new_element)
if old_element in old_candidate_list: if old_element in old_candidate_list:
old_candidate_list.remove(old_element) old_candidate_list.remove(old_element)
if old_index == new_index:
break
elif old_index < new_index:
misplaced_node_dict = misplaced_node_dict_after
else:
misplaced_node_dict = misplaced_node_dict_before
previous_new_element = new_element.getprevious()
for key, preceding_value_list in misplaced_node_dict.items():
for element_tuple in preceding_value_list:
if previous_new_element == element_tuple[1]:
#reuse the same previous as much as possible
if key is not None:
previous_new_element = previous_new_element.getparent()[key]
else:
previous_new_element = None
break
if previous_new_element is not None:
index_key_on_new_tree = previous_new_element.getparent().index(previous_new_element)
else:
index_key_on_new_tree = None
misplaced_node_dict.setdefault(index_key_on_new_tree, []).append((old_element, new_element))
break break
return old_candidate_list, new_candidate_list
# Chosse the lighter one to minimise diff
after_dict_weight = sum(len(i) for i in misplaced_node_dict_after.values())
before_dict_weight = sum(len(i) for i in misplaced_node_dict_before.values())
if after_dict_weight > before_dict_weight and before_dict_weight:
misplaced_node_dict = misplaced_node_dict_before
elif after_dict_weight <= before_dict_weight and after_dict_weight:
misplaced_node_dict = misplaced_node_dict_after
else:
misplaced_node_dict = {}
for k, v in misplaced_node_dict.items():
if k in old_new_index_mapping:
value = misplaced_node_dict[k]
misplaced_node_dict[old_new_index_mapping[k]] = value
if k is not None:
#if the element which suppose to support insert-after does not exist in old_tree,
#its just an added node not an moving
#None means that the node will become first child, so keep it
del misplaced_node_dict[k]
return old_candidate_list, new_candidate_list, misplaced_node_dict
def _compareChildNodes(self, old_element, new_element, path): def _compareChildNodes(self, old_element, new_element, path):
...@@ -473,13 +575,13 @@ class ERP5Diff: ...@@ -473,13 +575,13 @@ class ERP5Diff:
new_text = self._aggregateText(new_element) new_text = self._aggregateText(new_element)
if old_text != new_text: if old_text != new_text:
self._p("They differ, so update the elements.") self._p("They differ, so update the elements.")
self._xupdateUpdateElement(new_element, path) self._xupdateUpdateElement(new_element, path, nsmap=new_element.nsmap)
else: else:
# The contents are elements. # The contents are elements.
self._p("Both have elements.") self._p("Both have elements.")
old_list = self._aggregateElements(old_element) old_list = self._aggregateElements(old_element)
new_list = self._aggregateElements(new_element) new_list = self._aggregateElements(new_element)
old_list, new_list = self._removeStrictEqualsSubNodeList(old_list, new_list) old_list, new_list, misplaced_node_dict = self._removeStrictEqualsSubNodeList(old_list, new_list)
path_list = self._makeRelativePathList(old_list) path_list = self._makeRelativePathList(old_list)
new_start = 0 new_start = 0
new_len = len(new_list) new_len = len(new_list)
...@@ -498,6 +600,8 @@ class ERP5Diff: ...@@ -498,6 +600,8 @@ class ERP5Diff:
if new_len > new_start: if new_len > new_start:
# There are remaining nodes in the new children. # There are remaining nodes in the new children.
self._xupdateAppendElements(new_list[new_start:new_len], path) self._xupdateAppendElements(new_list[new_start:new_len], path)
if misplaced_node_dict:
self._xupdateMoveElements(misplaced_node_dict, path)
def compare(self, old_xml, new_xml): def compare(self, old_xml, new_xml):
""" """
......
...@@ -789,7 +789,157 @@ does not work as bellow example. This is a known bug. ...@@ -789,7 +789,157 @@ does not work as bellow example. This is a known bug.
<xupdate:update xmlns:prefix="http://any_uri" xmlns:aaa="http://www.erp5.org/namspaces/aaa" select="/aaa:erp5/object/title/attribute::prefix:attr">B</xupdate:update> <xupdate:update xmlns:prefix="http://any_uri" xmlns:aaa="http://www.erp5.org/namspaces/aaa" select="/aaa:erp5/object/title/attribute::prefix:attr">B</xupdate:update>
</xupdate:modifications> </xupdate:modifications>
26. Reorder some nodes to the end of list
>>> old_xml = """
... <ul>
... <li>1</li>
... <li>2</li>
... <li>3</li>
... <li>4</li>
... <li>5</li>
... <li>6</li>
... <li>7</li>
... <li>8</li>
... <li>9</li>
... </ul>
... """
>>> new_xml = """
... <ul>
... <li>1</li>
... <li>2</li>
... <li>5</li>
... <li>6</li>
... <li>7</li>
... <li>3</li>
... <li>4</li>
... <li>8</li>
... <li>9</li>
... </ul>
... """
>>> erp5diff.compare(old_xml, new_xml)
>>> erp5diff.output()
<xupdate:modifications xmlns:xupdate="http://www.xmldb.org/xupdate" version="1.0">
<xupdate:remove select="/ul/li[3]"/>
<xupdate:remove select="/ul/li[4]"/>
<xupdate:insert-after select="/ul/li[7]">
<xupdate:element name="li">3</xupdate:element>
<xupdate:element name="li">4</xupdate:element>
</xupdate:insert-after>
</xupdate:modifications>
26. Reorder some nodes from the end of list
>>> old_xml = """
... <ul>
... <li>1</li>
... <li>2</li>
... <li>3</li>
... <li>4</li>
... <li>5</li>
... <li>6</li>
... <li>7</li>
... <li>8</li>
... <li>9</li>
... </ul>
... """
>>> new_xml = """
... <ul>
... <li>1</li>
... <li>2</li>
... <li>7</li>
... <li>8</li>
... <li>3</li>
... <li>4</li>
... <li>5</li>
... <li>6</li>
... <li>9</li>
... </ul>
... """
>>> erp5diff.compare(old_xml, new_xml)
>>> erp5diff.output()
<xupdate:modifications xmlns:xupdate="http://www.xmldb.org/xupdate" version="1.0">
<xupdate:remove select="/ul/li[7]"/>
<xupdate:remove select="/ul/li[8]"/>
<xupdate:insert-after select="/ul/li[2]">
<xupdate:element name="li">7</xupdate:element>
<xupdate:element name="li">8</xupdate:element>
</xupdate:insert-after>
</xupdate:modifications>
27. Reorder some nodes at start
>>> old_xml = """
... <ul>
... <li>1</li>
... <li>2</li>
... <li>3</li>
... <li>4</li>
... <li>5</li>
... <li>6</li>
... <li>7</li>
... <li>8</li>
... <li>9</li>
... </ul>
... """
>>> new_xml = """
... <ul>
... <li>5</li>
... <li>6</li>
... <li>1</li>
... <li>2</li>
... <li>3</li>
... <li>4</li>
... <li>7</li>
... <li>8</li>
... <li>9</li>
... </ul>
... """
>>> erp5diff.compare(old_xml, new_xml)
>>> erp5diff.output()
<xupdate:modifications xmlns:xupdate="http://www.xmldb.org/xupdate" version="1.0">
<xupdate:remove select="/ul/li[5]"/>
<xupdate:remove select="/ul/li[6]"/>
<xupdate:append child="first()">
<xupdate:element name="li">5</xupdate:element>
<xupdate:element name="li">6</xupdate:element>
</xupdate:append>
</xupdate:modifications>
28. Reorder some nodes at the end
>>> old_xml = """
... <ul>
... <li>1</li>
... <li>2</li>
... <li>3</li>
... <li>4</li>
... <li>5</li>
... <li>6</li>
... <li>7</li>
... <li>8</li>
... <li>9</li>
... </ul>
... """
>>> new_xml = """
... <ul>
... <li>1</li>
... <li>4</li>
... <li>5</li>
... <li>6</li>
... <li>7</li>
... <li>8</li>
... <li>9</li>
... <li>2</li>
... <li>3</li>
... </ul>
... """
>>> erp5diff.compare(old_xml, new_xml)
>>> erp5diff.output()
<xupdate:modifications xmlns:xupdate="http://www.xmldb.org/xupdate" version="1.0">
<xupdate:remove select="/ul/li[2]"/>
<xupdate:remove select="/ul/li[3]"/>
<xupdate:insert-after select="/ul/li[9]">
<xupdate:element name="li">2</xupdate:element>
<xupdate:element name="li">3</xupdate:element>
</xupdate:insert-after>
</xupdate:modifications>
- 2003-12-04, Yoshinori OKUJI <yo@nexedi.com> - 2003-12-04, Yoshinori OKUJI <yo@nexedi.com>
- 2009-09-15, Tatuya Kamada <tatuya@nexedi.com> - 2009-09-15, Tatuya Kamada <tatuya@nexedi.com>
- 2009-01-12, Nicolas Delaby <nicolas@nexedi.com>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment