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:
child_element.text = element.text
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):
"""
Test if two given elements are matching. Matching does not mean that they are identical.
......@@ -406,17 +450,31 @@ class ERP5Diff:
def _removeStrictEqualsSubNodeList(self, old_list, new_list):
"""Remove inside list all elements which are similar
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[:]
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_c14n = StringIO()
old_tree.write_c14n(old_c14n)
old_c14n.seek(0)
for new_element in new_list:
new_index = new_list.index(new_element)
if new_element not in new_candidate_list:
continue
continue
new_tree = deepcopy(new_element).getroottree()
new_c14n = StringIO()
new_tree.write_c14n(new_c14n)
......@@ -430,12 +488,56 @@ class ERP5Diff:
file_equality = False
old_c14n.seek(0)
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:
new_candidate_list.remove(new_element)
if old_element in old_candidate_list:
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
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):
......@@ -473,13 +575,13 @@ class ERP5Diff:
new_text = self._aggregateText(new_element)
if old_text != new_text:
self._p("They differ, so update the elements.")
self._xupdateUpdateElement(new_element, path)
self._xupdateUpdateElement(new_element, path, nsmap=new_element.nsmap)
else:
# The contents are elements.
self._p("Both have elements.")
old_list = self._aggregateElements(old_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)
new_start = 0
new_len = len(new_list)
......@@ -498,6 +600,8 @@ class ERP5Diff:
if new_len > new_start:
# There are remaining nodes in the new children.
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):
"""
......
......@@ -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: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>
- 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