[Erp5-report] r42660 nicolas - in /erp5/trunk/utils/erp5diff: ./ src/ src/ERP5Diff/

nobody at svn.erp5.org nobody at svn.erp5.org
Tue Jan 25 16:11:59 CET 2011


Author: nicolas
Date: Tue Jan 25 16:11:58 2011
New Revision: 42660

URL: http://svn.erp5.org?rev=42660&view=rev
Log:
Fix installation of egg and provide backward compatibility for importing path

Added:
    erp5/trunk/utils/erp5diff/src/ERP5Diff/
    erp5/trunk/utils/erp5diff/src/ERP5Diff/ERP5Diff.py
    erp5/trunk/utils/erp5diff/src/ERP5Diff/__init__.py
Removed:
    erp5/trunk/utils/erp5diff/src/ERP5Diff.py
Modified:
    erp5/trunk/utils/erp5diff/CHANGES.txt
    erp5/trunk/utils/erp5diff/setup.py

Modified: erp5/trunk/utils/erp5diff/CHANGES.txt
URL: http://svn.erp5.org/erp5/trunk/utils/erp5diff/CHANGES.txt?rev=42660&r1=42659&r2=42660&view=diff
==============================================================================
--- erp5/trunk/utils/erp5diff/CHANGES.txt [utf8] (original)
+++ erp5/trunk/utils/erp5diff/CHANGES.txt [utf8] Tue Jan 25 16:11:58 2011
@@ -1,6 +1,10 @@
-0.8.1.2 (unreleased)
+0.8.1.3 (unreleased)
 ------------------
 
+0.8.1.2 (2011/01/25)
+------------------
+ * [fix] installation of egg
+
 0.8.1.1 (2011/01/25)
 ------------------
  * [Fix] position starts to 1

Modified: erp5/trunk/utils/erp5diff/setup.py
URL: http://svn.erp5.org/erp5/trunk/utils/erp5diff/setup.py?rev=42660&r1=42659&r2=42660&view=diff
==============================================================================
--- erp5/trunk/utils/erp5diff/setup.py [utf8] (original)
+++ erp5/trunk/utils/erp5diff/setup.py [utf8] Tue Jan 25 16:11:58 2011
@@ -5,7 +5,7 @@ from setuptools import setup, find_packa
 import re
 
 api_version = re.search(r'\s*__version__\s*=\s*(\S+)',
-                        open('src/ERP5Diff.py').read()).group(1).strip()
+                        open('src/ERP5Diff/ERP5Diff.py').read()).group(1).strip()
 revision = 2
 version = '%s.%s' % (api_version.replace("'", ''), revision)
 

Removed: erp5/trunk/utils/erp5diff/src/ERP5Diff.py
URL: http://svn.erp5.org/erp5/trunk/utils/erp5diff/src/ERP5Diff.py?rev=42659&view=auto
==============================================================================
--- erp5/trunk/utils/erp5diff/src/ERP5Diff.py [utf8] (original)
+++ erp5/trunk/utils/erp5diff/src/ERP5Diff.py (removed)
@@ -1,742 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# Yoshinori OKUJI <yo at nexedi.com>
-#
-# Copyright (C) 2003 Nexedi SARL
-#
-# This program is Free Software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ?See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA ?02111-1307, USA.
-#
-##############################################################################
-
-from lxml import etree
-parser = etree.XMLParser(remove_blank_text=True)
-
-import sys
-import getopt
-import os
-try:
-  from cStringIO import StringIO
-except ImportError:
-  from StringIO import StringIO
-import re
-import codecs
-from copy import deepcopy
-from interfaces.erp5diff import IERP5Diff
-import zope.interface
-
-def isNodeEquals(old, new):
-  if old.tag != new.tag or old.attrib != new.attrib:
-    return False
-  if old.text != new.text or old.tail != new.tail:
-    return False
-  if len(old) != len(new):
-    return False
-  for old_child, new_child in zip(old, new):
-    if not isNodeEquals(old_child, new_child):
-      return False
-  return True
-
-class ERP5Diff:
-  """
-    Make a difference between two XML documents using XUpdate.
-    Use some assumptions in ERP5's data representation.
-
-    The strategy is:
-      1. Find a matching element among elements of the other XML document at the same depth.
-      2. Use the first matching element, even if there can be other better elements.
-      3. Assume that two elements are matching, if the tag names are identical. If either of
-         them has an attribute 'id', the values of the attributes 'id' also must be identical.
-      4. Don't use xupdate:rename for elements. It should be quite rare to rename tag names
-         in ERP5, and it is too complicated to support this renaming.
-      5. Ignore some types of nodes, such as EntityReference and Comment, because they are not
-         used in ERP5 XML documents.
-  """
-
-  # Declarative interfaces
-  zope.interface.implements(IERP5Diff,)
-
-  __version__ = '0.8.1'
-
-  def __init__(self):
-    """
-      Initialize itself.
-    """
-    self._verbose = 0
-    self._result = None
-    self._ns = 'http://www.xmldb.org/xupdate'
-
-  def setVerbosity(self, verbose):
-    """
-      Set the verbosity.
-    """
-    self._verbose = verbose
-
-  def _p(self, msg):
-    """
-      Print a message only if being verbose.
-    """
-    if self._verbose:
-      sys.stderr.write(str(msg) + os.linesep)
-
-  def _makeDocList(self, *args):
-    """
-      Make a list of Document objects.
-    """
-    doc_list = []
-    for a in args:
-      if isinstance(a, str):
-        doc_list.append(etree.fromstring(a, parser))
-      else:
-        element_tree = etree.parse(a, parser)
-        doc_list.append(element_tree.getroot())
-    return doc_list
-
-  def _concatPath(self, p1, p2, separator='/'):
-    """
-      Concatenate 'p1' and 'p2'. Add a separator between them,
-      only if 'p1' does not end with a separator.
-    """
-    if p1.endswith(separator):
-      return p1 + p2
-    return p1 + separator + p2
-
-  def _getResultRoot(self):
-    """
-      Return the root element of the result document.
-    """
-    return self._result
-    #return self._result.getroottree()
-
-  def _hasChildren(self, element):
-    """
-      Check whether the element has any children
-    """
-    return bool(len(element))
-
-  def _getQName(self, element, attr_name):
-    """Return qualified name compatible with xpath
-    """
-    if '{' == attr_name[0]:
-      #This is a Qualified attribute
-      index = attr_name.index('}')
-      local_name = attr_name[index+1:]
-      namespace_uri = attr_name[1:index]
-      if namespace_uri == 'http://www.w3.org/XML/1998/namespace':
-        prefix = 'xml'
-      else:
-        prefix = [t[0] for t in element.nsmap.iteritems() if t[1] == namespace_uri][0]
-      return '%s:%s' % (prefix, local_name,), namespace_uri
-    else:
-      return attr_name, None
-
-  def _xupdateAppendAttributes(self, attr_dict, path, nsmap=None):
-    """
-      Append attrib to the element at 'path'.
-    """
-    root = self._getResultRoot()
-    append_element = etree.Element('{%s}append' % self._ns, nsmap=root.nsmap)
-    append_element.attrib['select'] = path
-    key_list = attr_dict.keys()
-    key_list.sort()
-    for name in key_list:
-      val = attr_dict[name]
-      attr_element = etree.Element('{%s}attribute' % self._ns, nsmap=nsmap)
-      name, namespace_uri = name
-      attr_element.attrib['name'] = name
-      if namespace_uri:
-        attr_element.attrib['namespace'] = namespace_uri
-      attr_element.text = val
-      append_element.append(attr_element)
-    root.append(append_element)
-
-  def _xupdateRemoveAttribute(self, name, path, nsmap=None):
-    """
-      Remove an attribute from the element at 'path'.
-    """
-    root = self._getResultRoot()
-    remove_element = etree.Element('{%s}remove' % self._ns, nsmap=nsmap)
-    remove_element.attrib['select'] = self._concatPath(path, 'attribute::' + name[0])
-    root.append(remove_element)
-
-  def _xupdateUpdateAttribute(self, name, val, path, nsmap=None):
-    """
-      Update the value of an attribute of the element at 'path'.
-    """
-    root = self._getResultRoot()
-    update_element = etree.Element('{%s}update' % self._ns, nsmap=nsmap)
-    update_element.attrib['select'] = self._concatPath(path, 'attribute::' + name[0])
-    update_element.text = val
-    root.append(update_element)
-
-  def _xupdateRenameElement(self, name, path, nsmap=None):
-    """
-      Rename an existing element at 'path'.
-    """
-    root = self._getResultRoot()
-    rename_element = etree.Element('{%s}rename' % self._ns, nsmap=nsmap)
-    rename_element.attrib['select'] = path
-    rename_element.text = name
-    root.append(rename_element)
-
-  def _xupdateUpdateElement(self, element, path, nsmap=None):
-    """
-      Update the contents of an element at 'path' to that of 'element'.
-    """
-    root = self._getResultRoot()
-    update_element = etree.Element('{%s}update' % self._ns, nsmap=nsmap)
-    update_element.attrib['select'] = path 
-    if self._hasChildren(element):
-      for child in element:
-        clone_node = deepcopy(child)
-        update_element.append(clone_node)
-    else:
-      update_element.text = element.text
-    root.append(update_element)
-
-  def _xupdateRemoveElement(self, path, nsmap=None):
-    """
-      Remove an element at 'path'.
-    """
-    root = self._getResultRoot()
-    remove_element = etree.Element('{%s}remove' % self._ns, nsmap=nsmap)
-    remove_element.attrib['select'] = path
-    root.append(remove_element)
-
-  def _xupdateAppendElements(self, element_list, path):
-    """
-      Append elements to the element at 'path'.
-      xupdate:append
-      xupdate:insert-before
-      xupdate:insert-after
-    """
-    root = self._getResultRoot()
-    if not element_list:
-      return
-    parent_element = element_list[0].getparent()
-    len_total_child_list = len(parent_element)
-    last_append_element = None
-    for element in element_list:
-      # get only elements not something else (PI and comments are ignored)
-      # XXX May be support of PI and Comments should be added
-      # in this case fallback to previous code
-      # relative_next = element.getnext()
-      relative_next_list = element.xpath('following-sibling::*[1]')
-      if relative_next_list:
-        relative_next = relative_next_list[0]
-      else:
-        relative_next = None
-      relative_previous_list = element.xpath('preceding-sibling::*[1]')
-      if relative_previous_list:
-        relative_previous = relative_previous_list[0]
-      else:
-        relative_previous = None
-      if relative_previous in element_list:
-        #reuse same container as preceding
-        append_element = last_append_element
-      elif relative_next is not None and relative_next not in element_list:
-        append_element = etree.SubElement(root, '{%s}insert-before' % self._ns, nsmap=element.nsmap)
-        path_list = self._makeRelativePathList([relative_next], before=1)
-        next_sibling_path = self._concatPath(path, path_list[0])
-        append_element.attrib['select'] = next_sibling_path
-      elif relative_previous is not None and relative_previous not in element_list:
-        append_element = etree.SubElement(root, '{%s}insert-after' % self._ns, nsmap=element.nsmap)
-        path_list = self._makeRelativePathList([relative_previous])
-        preceding_sibling_path = self._concatPath(path, path_list[0])
-        append_element.attrib['select'] = preceding_sibling_path
-      else:
-        #xupdate:append by default
-        append_element = etree.SubElement(root, '{%s}append' % self._ns, nsmap=element.nsmap)
-        if parent_element.index(element) == 0:
-          child = 'first()'
-        elif parent_element.index(element) == (len_total_child_list -1):
-          child = 'last()'
-        else:
-          child = '%d' % (len_total_child_list - parent_element.index(element) + 1)
-        append_element.attrib.update({'select': path,
-                                      'child': child})
-      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
-      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.
-    """
-    # Make sure that they are elements.
-    if type(element1) !=  type(element2) or type(element1) != etree._Element:
-      return False
-
-    if element1.tag != element2.tag:
-      return False
-
-    id_list = []
-    for attr_map in (element1.attrib, element2.attrib):
-      if 'id' in attr_map:
-        id_list.append(attr_map['id'])
-
-    if len(id_list) == 0:
-      return True
-    if len(id_list) == 1:
-      return False
-    return (id_list[0] == id_list[1])
-
-  def _testAttributes(self, element1, element2, path):
-    """
-      Test attrib of two given elements. Add differences, if any.
-    """
-    # Make a list of dictionaries of the attributes.
-    dict_list = []
-    for element in (element1, element2):
-      d = {}
-      for name, value in element.attrib.items():
-        name, namespace_uri = self._getQName(element, name)
-        d[(name, namespace_uri)] = value
-      dict_list.append(d)
-    dict1, dict2 = dict_list
-
-    # Find all added or removed or changed attrib.
-    #sort key list to stick expected output
-    key_list1 = dict1.keys()
-    key_list1.sort()
-    for name1 in key_list1:
-      val1 = dict1[name1]
-      if name1 in dict2:
-        if val1 != dict2[name1]:
-          # The value is different.
-          self._xupdateUpdateAttribute(name1, dict2[name1], path, nsmap=element.nsmap)
-        # Mark this attribute.
-        dict2[name1] = None
-      else:
-        # This attribute is removed.
-        self._xupdateRemoveAttribute(name1, path, nsmap=element.nsmap)
-    d = {}
-    for name2, val2 in dict2.iteritems():
-      if val2 is not None:
-        # This attribute is added.
-        d[name2] = val2
-    if d != {}:
-      self._xupdateAppendAttributes(d, path, nsmap=element.nsmap)
-
-  def _checkEmptiness(self, element):
-    """
-      Check if an element has Element or Text nodes
-    """
-    for child in element:
-      if type(child) == etree._Element:
-        return False
-    if element.text is not None:
-      return False
-    return True
-
-  def _checkIgnoreText(self, element):
-    """
-      Determine if text should be ignored by heuristics,
-      because ERP5 does not define any schema at the moment.
-      We ignore white-space text nodes between elements.
-      pseudo code:
-      tree = parse("
-      <node>
-         </node>")
-      tree.node.text == '\n    '
-    """
-    for child in element:
-      if type(child) == etree._Element:
-        return True
-    if element.text is None:
-      return True
-    return bool(element.text.strip()) is False or False
-
-  def _makeRelativePathList(self, element_list, before=0):
-    """
-      Make a list of relative paths from a list of elements.
-    """
-
-    path_list = []
-    for element in element_list:
-      # Check if this element has an attribute 'id'.s
-      id_val = None
-      attr_map = element.attrib
-      for name, value in attr_map.items():
-        if name in ('id', 'gid',):
-          id_val = value
-          id_of_id = name
-          break
-
-      if id_val is not None:
-        # If an attribute 'id' or 'gid' is present, uses the attribute for convenience.
-        position_predicate = ''
-        len_all_similar_sibling = len(element.xpath('../*[@%s = "%s"]' %\
-                                                           (id_of_id, id_val)))
-        if len_all_similar_sibling > 1:
-          position = len_all_similar_sibling - \
-              element.xpath('count(following-sibling::%s[@%s = "%s"])' %\
-                                   (element.xpath('name()'), id_of_id, id_val),
-                                                      namespaces=element.nsmap)
-          position_predicate = '[%i]' % position
-        path_list.append("%s[@%s='%s']%s" % (element.xpath('name()'), id_of_id,
-                                                  id_val, position_predicate,))
-        # Increase the count, for a case where other elements with the same tag name do not have
-        # 'id' attrib.
-      else:
-        len_all_similar_sibling = len(element.findall('../%s' % element.tag))
-        if len_all_similar_sibling > 1:
-          position = len_all_similar_sibling - len(list(element.itersiblings(tag=element.tag)))
-          path_list.append('%s[%d]' % (element.xpath('name()'), position-before or 1))
-        else:
-          path_list.append(element.xpath('name()'))
-
-    return path_list
-
-  def _aggregateElements(self, element):
-    """
-      Aggregate child elements of an element into a list.
-    """
-    return [child for child in element if type(child) == etree._Element]
-
-  def _aggregateText(self, element):
-    """
-      Aggregate child text nodes of an element into a single string.
-    """
-    return '%s' % element.xpath('string(.)')
-
-  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[:]
-    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
-      for new_element in new_list:
-        new_index = new_list.index(new_element)
-        if new_element not in new_candidate_list:
-          continue
-        node_equality = isNodeEquals(old_element, new_element)
-        if node_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
-
-    # 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):
-    """
-      Compare children of two elements, and add differences into the result, if any.
-      Call itself recursively, if these elements have grandchilden.
-    """
-    self._p("Comparing %s with %s at %s..." % (repr(old_element), repr(new_element), path))
-
-    # First, determine if they are empty.
-    old_is_empty = self._checkEmptiness(old_element)
-    new_is_empty = self._checkEmptiness(new_element)
-
-    if old_is_empty and new_is_empty:
-      # Nothing to do.
-      self._p("Both are empty.")
-      pass
-    else:
-      # Second, determine if text should be ignored.
-      old_ignore_text = self._checkIgnoreText(old_element)
-      new_ignore_text = self._checkIgnoreText(new_element)
-
-      if old_ignore_text != new_ignore_text:
-        # This means that the semantics of this element is quite different.
-        self._p("One of them has only text and the other does not, so just update all the contents.")
-        self._xupdateUpdateElement(new_element, path, nsmap=new_element.nsmap)
-      elif not old_ignore_text:
-        # The contents are only text.
-        self._p("Both have only text.")
-        old_text = self._aggregateText(old_element)
-        new_text = self._aggregateText(new_element)
-        if old_text != new_text:
-          self._p("They differ, so update the elements.")
-          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, misplaced_node_dict = self._removeStrictEqualsSubNodeList(old_list, new_list)
-        path_list = self._makeRelativePathList(old_list)
-        new_start = 0
-        new_len = len(new_list)
-        # Usefull set to detect orphan in new_list
-        new_object_left_index_set = set()
-        for old_node, node_path in zip(old_list, path_list):
-          child_path = self._concatPath(path, node_path)
-          for new_current in range(new_start, new_len):
-            new_node = new_list[new_current]
-            if self._testElements(old_node, new_node):
-              self._testAttributes(old_node, new_node, child_path)
-              self._compareChildNodes(old_node, new_node, child_path)
-              new_start = new_current + 1
-              if new_current in new_object_left_index_set:
-                new_object_left_index_set.remove(new_current)
-              break
-            else:
-              new_object_left_index_set.add(new_current)
-          else:
-            # There is no matching node. So this element must be removed.
-            self._xupdateRemoveElement(child_path, old_node.nsmap)
-        if new_len > new_start:
-          # There are remaining nodes in the new children.
-          self._xupdateAppendElements(new_list[new_start:new_len], path)
-          # if New children are allready added, clean up new_object_left_index_set
-          [new_object_left_index_set.remove(index)\
-           for index in range(new_start, new_len) if\
-           index in new_object_left_index_set]
-        if new_object_left_index_set:
-          self._xupdateAppendElements([new_list[index] for index \
-                                           in new_object_left_index_set], path)
-        if misplaced_node_dict:
-          self._xupdateMoveElements(misplaced_node_dict, path)
-
-  def compare(self, old_xml, new_xml):
-    """
-      Compare two given XML documents.
-      If an argument is a string, it is assumed to be a XML document itself.
-      Otherwise, it is assumed to be a file object which contains a XML document.
-    """
-    old_doc, new_doc = self._makeDocList(old_xml, new_xml)
-    old_root_element = old_doc.getroottree().getroot()
-    new_root_element = new_doc.getroottree().getroot()
-    try:
-      if self._result is not None:
-        self._result = None 
-      self._result = etree.Element('{%s}modifications' % self._ns, nsmap={'xupdate': self._ns})
-      self._result.set('version', '1.0')
-      if self._testElements(old_root_element, new_root_element):
-        qname = old_root_element.xpath('name()')
-        self._testAttributes(old_root_element, new_root_element, '/%s' % qname)
-        self._compareChildNodes(old_root_element, new_root_element, '/%s' % qname)
-      else:
-        # These XML documents seem to be completely different...
-        if old_root_element.tag != new_root_element.tag:
-          nsmap = old_root_element.nsmap
-          nsmap.update(new_root_element.nsmap)
-          self._xupdateRenameElement(new_root_element.xpath('name()'), '/%s' % old_root_element.xpath('name()'), nsmap)
-        qname = new_root_element.xpath('name()')
-        self._testAttributes(old_root_element, new_root_element, '/%s' % qname)
-        self._compareChildNodes(old_root_element, new_root_element, '/%s' % qname)
-    finally:
-      del old_doc
-      del new_doc
-
-  def output(self, output_file=None):
-    """
-      Output the result of parsing XML documents to 'output_file'.
-      If it is not specified, stdout is assumed.
-    """
-    if output_file is None:
-      output_file = sys.stdout
-    # stream
-    xml = etree.tostring(self._result, encoding='utf-8', pretty_print=True)
-    output_file.write(xml)
-
-  def outputString(self):
-    """
-      Return the result as a string object.
-    """
-    io = StringIO()
-    self.output(io)
-    ret = io.getvalue()
-    io.close()
-    return ret
-
-def main():
-  """
-    The main routine of ERP5Diff.
-  """
-  try:
-    opts, args = getopt.getopt(sys.argv[1:], "ho:v", ["help", "output=", "verbose"])
-  except getopt.GetoptError, msg:
-    print msg
-    print "Try ``erp5diff --help'' for more information."
-    sys.exit(2)
-  output = None
-  verbose = 0
-  for o, a in opts:
-    if o == "-v":
-      verbose = 1
-    elif o in ("-h", "--help"):
-      print '''Usage: erp5diff [OPTION]... OLD_XML NEW_XML
-Make a difference between two XML documents in XUpdate format.
-
-    -h, --help          display this message and exit
-    -o, --output=FILE   output the result to the file FILE
-    -v, --verbose       print verbose messages
-
-Report bugs to <yo at nexedi.com>.'''
-      sys.exit()
-    elif o in ("-o", "--output"):
-      output = a
-
-  if len(args) != 2:
-    if len(args) > 2:
-      print "Too many arguments."
-    else:
-      print "Too few arguments."
-    print "Try ``erp5diff --help'' for more information."
-    sys.exit(2)
-
-  d = ERP5Diff()
-  d.setVerbosity(verbose)
-
-  old_xml = open(args[0])
-  new_xml = open(args[1])
-  d.compare(old_xml, new_xml)
-  old_xml.close()
-  new_xml.close()
-
-  try:
-    if output is not None:
-      file = open(output, 'w')
-    else:
-      file = None
-    d.output(file)
-  except:
-    if output is not None:
-      file.close()
-      os.remove(output)
-    raise
-  else:
-    if file is not None:
-      file.close()
-
-  sys.exit()
-
-if __name__ == '__main__':
-  main()

Added: erp5/trunk/utils/erp5diff/src/ERP5Diff/ERP5Diff.py
URL: http://svn.erp5.org/erp5/trunk/utils/erp5diff/src/ERP5Diff/ERP5Diff.py?rev=42660&view=auto
==============================================================================
--- erp5/trunk/utils/erp5diff/src/ERP5Diff/ERP5Diff.py (added)
+++ erp5/trunk/utils/erp5diff/src/ERP5Diff/ERP5Diff.py [utf8] Tue Jan 25 16:11:58 2011
@@ -0,0 +1,742 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Yoshinori OKUJI <yo at nexedi.com>
+#
+# Copyright (C) 2003 Nexedi SARL
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ?See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA ?02111-1307, USA.
+#
+##############################################################################
+
+from lxml import etree
+parser = etree.XMLParser(remove_blank_text=True)
+
+import sys
+import getopt
+import os
+try:
+  from cStringIO import StringIO
+except ImportError:
+  from StringIO import StringIO
+import re
+import codecs
+from copy import deepcopy
+from interfaces.erp5diff import IERP5Diff
+import zope.interface
+
+def isNodeEquals(old, new):
+  if old.tag != new.tag or old.attrib != new.attrib:
+    return False
+  if old.text != new.text or old.tail != new.tail:
+    return False
+  if len(old) != len(new):
+    return False
+  for old_child, new_child in zip(old, new):
+    if not isNodeEquals(old_child, new_child):
+      return False
+  return True
+
+class ERP5Diff:
+  """
+    Make a difference between two XML documents using XUpdate.
+    Use some assumptions in ERP5's data representation.
+
+    The strategy is:
+      1. Find a matching element among elements of the other XML document at the same depth.
+      2. Use the first matching element, even if there can be other better elements.
+      3. Assume that two elements are matching, if the tag names are identical. If either of
+         them has an attribute 'id', the values of the attributes 'id' also must be identical.
+      4. Don't use xupdate:rename for elements. It should be quite rare to rename tag names
+         in ERP5, and it is too complicated to support this renaming.
+      5. Ignore some types of nodes, such as EntityReference and Comment, because they are not
+         used in ERP5 XML documents.
+  """
+
+  # Declarative interfaces
+  zope.interface.implements(IERP5Diff,)
+
+  __version__ = '0.8.1'
+
+  def __init__(self):
+    """
+      Initialize itself.
+    """
+    self._verbose = 0
+    self._result = None
+    self._ns = 'http://www.xmldb.org/xupdate'
+
+  def setVerbosity(self, verbose):
+    """
+      Set the verbosity.
+    """
+    self._verbose = verbose
+
+  def _p(self, msg):
+    """
+      Print a message only if being verbose.
+    """
+    if self._verbose:
+      sys.stderr.write(str(msg) + os.linesep)
+
+  def _makeDocList(self, *args):
+    """
+      Make a list of Document objects.
+    """
+    doc_list = []
+    for a in args:
+      if isinstance(a, str):
+        doc_list.append(etree.fromstring(a, parser))
+      else:
+        element_tree = etree.parse(a, parser)
+        doc_list.append(element_tree.getroot())
+    return doc_list
+
+  def _concatPath(self, p1, p2, separator='/'):
+    """
+      Concatenate 'p1' and 'p2'. Add a separator between them,
+      only if 'p1' does not end with a separator.
+    """
+    if p1.endswith(separator):
+      return p1 + p2
+    return p1 + separator + p2
+
+  def _getResultRoot(self):
+    """
+      Return the root element of the result document.
+    """
+    return self._result
+    #return self._result.getroottree()
+
+  def _hasChildren(self, element):
+    """
+      Check whether the element has any children
+    """
+    return bool(len(element))
+
+  def _getQName(self, element, attr_name):
+    """Return qualified name compatible with xpath
+    """
+    if '{' == attr_name[0]:
+      #This is a Qualified attribute
+      index = attr_name.index('}')
+      local_name = attr_name[index+1:]
+      namespace_uri = attr_name[1:index]
+      if namespace_uri == 'http://www.w3.org/XML/1998/namespace':
+        prefix = 'xml'
+      else:
+        prefix = [t[0] for t in element.nsmap.iteritems() if t[1] == namespace_uri][0]
+      return '%s:%s' % (prefix, local_name,), namespace_uri
+    else:
+      return attr_name, None
+
+  def _xupdateAppendAttributes(self, attr_dict, path, nsmap=None):
+    """
+      Append attrib to the element at 'path'.
+    """
+    root = self._getResultRoot()
+    append_element = etree.Element('{%s}append' % self._ns, nsmap=root.nsmap)
+    append_element.attrib['select'] = path
+    key_list = attr_dict.keys()
+    key_list.sort()
+    for name in key_list:
+      val = attr_dict[name]
+      attr_element = etree.Element('{%s}attribute' % self._ns, nsmap=nsmap)
+      name, namespace_uri = name
+      attr_element.attrib['name'] = name
+      if namespace_uri:
+        attr_element.attrib['namespace'] = namespace_uri
+      attr_element.text = val
+      append_element.append(attr_element)
+    root.append(append_element)
+
+  def _xupdateRemoveAttribute(self, name, path, nsmap=None):
+    """
+      Remove an attribute from the element at 'path'.
+    """
+    root = self._getResultRoot()
+    remove_element = etree.Element('{%s}remove' % self._ns, nsmap=nsmap)
+    remove_element.attrib['select'] = self._concatPath(path, 'attribute::' + name[0])
+    root.append(remove_element)
+
+  def _xupdateUpdateAttribute(self, name, val, path, nsmap=None):
+    """
+      Update the value of an attribute of the element at 'path'.
+    """
+    root = self._getResultRoot()
+    update_element = etree.Element('{%s}update' % self._ns, nsmap=nsmap)
+    update_element.attrib['select'] = self._concatPath(path, 'attribute::' + name[0])
+    update_element.text = val
+    root.append(update_element)
+
+  def _xupdateRenameElement(self, name, path, nsmap=None):
+    """
+      Rename an existing element at 'path'.
+    """
+    root = self._getResultRoot()
+    rename_element = etree.Element('{%s}rename' % self._ns, nsmap=nsmap)
+    rename_element.attrib['select'] = path
+    rename_element.text = name
+    root.append(rename_element)
+
+  def _xupdateUpdateElement(self, element, path, nsmap=None):
+    """
+      Update the contents of an element at 'path' to that of 'element'.
+    """
+    root = self._getResultRoot()
+    update_element = etree.Element('{%s}update' % self._ns, nsmap=nsmap)
+    update_element.attrib['select'] = path 
+    if self._hasChildren(element):
+      for child in element:
+        clone_node = deepcopy(child)
+        update_element.append(clone_node)
+    else:
+      update_element.text = element.text
+    root.append(update_element)
+
+  def _xupdateRemoveElement(self, path, nsmap=None):
+    """
+      Remove an element at 'path'.
+    """
+    root = self._getResultRoot()
+    remove_element = etree.Element('{%s}remove' % self._ns, nsmap=nsmap)
+    remove_element.attrib['select'] = path
+    root.append(remove_element)
+
+  def _xupdateAppendElements(self, element_list, path):
+    """
+      Append elements to the element at 'path'.
+      xupdate:append
+      xupdate:insert-before
+      xupdate:insert-after
+    """
+    root = self._getResultRoot()
+    if not element_list:
+      return
+    parent_element = element_list[0].getparent()
+    len_total_child_list = len(parent_element)
+    last_append_element = None
+    for element in element_list:
+      # get only elements not something else (PI and comments are ignored)
+      # XXX May be support of PI and Comments should be added
+      # in this case fallback to previous code
+      # relative_next = element.getnext()
+      relative_next_list = element.xpath('following-sibling::*[1]')
+      if relative_next_list:
+        relative_next = relative_next_list[0]
+      else:
+        relative_next = None
+      relative_previous_list = element.xpath('preceding-sibling::*[1]')
+      if relative_previous_list:
+        relative_previous = relative_previous_list[0]
+      else:
+        relative_previous = None
+      if relative_previous in element_list:
+        #reuse same container as preceding
+        append_element = last_append_element
+      elif relative_next is not None and relative_next not in element_list:
+        append_element = etree.SubElement(root, '{%s}insert-before' % self._ns, nsmap=element.nsmap)
+        path_list = self._makeRelativePathList([relative_next], before=1)
+        next_sibling_path = self._concatPath(path, path_list[0])
+        append_element.attrib['select'] = next_sibling_path
+      elif relative_previous is not None and relative_previous not in element_list:
+        append_element = etree.SubElement(root, '{%s}insert-after' % self._ns, nsmap=element.nsmap)
+        path_list = self._makeRelativePathList([relative_previous])
+        preceding_sibling_path = self._concatPath(path, path_list[0])
+        append_element.attrib['select'] = preceding_sibling_path
+      else:
+        #xupdate:append by default
+        append_element = etree.SubElement(root, '{%s}append' % self._ns, nsmap=element.nsmap)
+        if parent_element.index(element) == 0:
+          child = 'first()'
+        elif parent_element.index(element) == (len_total_child_list -1):
+          child = 'last()'
+        else:
+          child = '%d' % (len_total_child_list - parent_element.index(element) + 1)
+        append_element.attrib.update({'select': path,
+                                      'child': child})
+      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
+      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.
+    """
+    # Make sure that they are elements.
+    if type(element1) !=  type(element2) or type(element1) != etree._Element:
+      return False
+
+    if element1.tag != element2.tag:
+      return False
+
+    id_list = []
+    for attr_map in (element1.attrib, element2.attrib):
+      if 'id' in attr_map:
+        id_list.append(attr_map['id'])
+
+    if len(id_list) == 0:
+      return True
+    if len(id_list) == 1:
+      return False
+    return (id_list[0] == id_list[1])
+
+  def _testAttributes(self, element1, element2, path):
+    """
+      Test attrib of two given elements. Add differences, if any.
+    """
+    # Make a list of dictionaries of the attributes.
+    dict_list = []
+    for element in (element1, element2):
+      d = {}
+      for name, value in element.attrib.items():
+        name, namespace_uri = self._getQName(element, name)
+        d[(name, namespace_uri)] = value
+      dict_list.append(d)
+    dict1, dict2 = dict_list
+
+    # Find all added or removed or changed attrib.
+    #sort key list to stick expected output
+    key_list1 = dict1.keys()
+    key_list1.sort()
+    for name1 in key_list1:
+      val1 = dict1[name1]
+      if name1 in dict2:
+        if val1 != dict2[name1]:
+          # The value is different.
+          self._xupdateUpdateAttribute(name1, dict2[name1], path, nsmap=element.nsmap)
+        # Mark this attribute.
+        dict2[name1] = None
+      else:
+        # This attribute is removed.
+        self._xupdateRemoveAttribute(name1, path, nsmap=element.nsmap)
+    d = {}
+    for name2, val2 in dict2.iteritems():
+      if val2 is not None:
+        # This attribute is added.
+        d[name2] = val2
+    if d != {}:
+      self._xupdateAppendAttributes(d, path, nsmap=element.nsmap)
+
+  def _checkEmptiness(self, element):
+    """
+      Check if an element has Element or Text nodes
+    """
+    for child in element:
+      if type(child) == etree._Element:
+        return False
+    if element.text is not None:
+      return False
+    return True
+
+  def _checkIgnoreText(self, element):
+    """
+      Determine if text should be ignored by heuristics,
+      because ERP5 does not define any schema at the moment.
+      We ignore white-space text nodes between elements.
+      pseudo code:
+      tree = parse("
+      <node>
+         </node>")
+      tree.node.text == '\n    '
+    """
+    for child in element:
+      if type(child) == etree._Element:
+        return True
+    if element.text is None:
+      return True
+    return bool(element.text.strip()) is False or False
+
+  def _makeRelativePathList(self, element_list, before=0):
+    """
+      Make a list of relative paths from a list of elements.
+    """
+
+    path_list = []
+    for element in element_list:
+      # Check if this element has an attribute 'id'.s
+      id_val = None
+      attr_map = element.attrib
+      for name, value in attr_map.items():
+        if name in ('id', 'gid',):
+          id_val = value
+          id_of_id = name
+          break
+
+      if id_val is not None:
+        # If an attribute 'id' or 'gid' is present, uses the attribute for convenience.
+        position_predicate = ''
+        len_all_similar_sibling = len(element.xpath('../*[@%s = "%s"]' %\
+                                                           (id_of_id, id_val)))
+        if len_all_similar_sibling > 1:
+          position = len_all_similar_sibling - \
+              element.xpath('count(following-sibling::%s[@%s = "%s"])' %\
+                                   (element.xpath('name()'), id_of_id, id_val),
+                                                      namespaces=element.nsmap)
+          position_predicate = '[%i]' % position
+        path_list.append("%s[@%s='%s']%s" % (element.xpath('name()'), id_of_id,
+                                                  id_val, position_predicate,))
+        # Increase the count, for a case where other elements with the same tag name do not have
+        # 'id' attrib.
+      else:
+        len_all_similar_sibling = len(element.findall('../%s' % element.tag))
+        if len_all_similar_sibling > 1:
+          position = len_all_similar_sibling - len(list(element.itersiblings(tag=element.tag)))
+          path_list.append('%s[%d]' % (element.xpath('name()'), position-before or 1))
+        else:
+          path_list.append(element.xpath('name()'))
+
+    return path_list
+
+  def _aggregateElements(self, element):
+    """
+      Aggregate child elements of an element into a list.
+    """
+    return [child for child in element if type(child) == etree._Element]
+
+  def _aggregateText(self, element):
+    """
+      Aggregate child text nodes of an element into a single string.
+    """
+    return '%s' % element.xpath('string(.)')
+
+  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[:]
+    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
+      for new_element in new_list:
+        new_index = new_list.index(new_element)
+        if new_element not in new_candidate_list:
+          continue
+        node_equality = isNodeEquals(old_element, new_element)
+        if node_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
+
+    # 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):
+    """
+      Compare children of two elements, and add differences into the result, if any.
+      Call itself recursively, if these elements have grandchilden.
+    """
+    self._p("Comparing %s with %s at %s..." % (repr(old_element), repr(new_element), path))
+
+    # First, determine if they are empty.
+    old_is_empty = self._checkEmptiness(old_element)
+    new_is_empty = self._checkEmptiness(new_element)
+
+    if old_is_empty and new_is_empty:
+      # Nothing to do.
+      self._p("Both are empty.")
+      pass
+    else:
+      # Second, determine if text should be ignored.
+      old_ignore_text = self._checkIgnoreText(old_element)
+      new_ignore_text = self._checkIgnoreText(new_element)
+
+      if old_ignore_text != new_ignore_text:
+        # This means that the semantics of this element is quite different.
+        self._p("One of them has only text and the other does not, so just update all the contents.")
+        self._xupdateUpdateElement(new_element, path, nsmap=new_element.nsmap)
+      elif not old_ignore_text:
+        # The contents are only text.
+        self._p("Both have only text.")
+        old_text = self._aggregateText(old_element)
+        new_text = self._aggregateText(new_element)
+        if old_text != new_text:
+          self._p("They differ, so update the elements.")
+          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, misplaced_node_dict = self._removeStrictEqualsSubNodeList(old_list, new_list)
+        path_list = self._makeRelativePathList(old_list)
+        new_start = 0
+        new_len = len(new_list)
+        # Usefull set to detect orphan in new_list
+        new_object_left_index_set = set()
+        for old_node, node_path in zip(old_list, path_list):
+          child_path = self._concatPath(path, node_path)
+          for new_current in range(new_start, new_len):
+            new_node = new_list[new_current]
+            if self._testElements(old_node, new_node):
+              self._testAttributes(old_node, new_node, child_path)
+              self._compareChildNodes(old_node, new_node, child_path)
+              new_start = new_current + 1
+              if new_current in new_object_left_index_set:
+                new_object_left_index_set.remove(new_current)
+              break
+            else:
+              new_object_left_index_set.add(new_current)
+          else:
+            # There is no matching node. So this element must be removed.
+            self._xupdateRemoveElement(child_path, old_node.nsmap)
+        if new_len > new_start:
+          # There are remaining nodes in the new children.
+          self._xupdateAppendElements(new_list[new_start:new_len], path)
+          # if New children are allready added, clean up new_object_left_index_set
+          [new_object_left_index_set.remove(index)\
+           for index in range(new_start, new_len) if\
+           index in new_object_left_index_set]
+        if new_object_left_index_set:
+          self._xupdateAppendElements([new_list[index] for index \
+                                           in new_object_left_index_set], path)
+        if misplaced_node_dict:
+          self._xupdateMoveElements(misplaced_node_dict, path)
+
+  def compare(self, old_xml, new_xml):
+    """
+      Compare two given XML documents.
+      If an argument is a string, it is assumed to be a XML document itself.
+      Otherwise, it is assumed to be a file object which contains a XML document.
+    """
+    old_doc, new_doc = self._makeDocList(old_xml, new_xml)
+    old_root_element = old_doc.getroottree().getroot()
+    new_root_element = new_doc.getroottree().getroot()
+    try:
+      if self._result is not None:
+        self._result = None 
+      self._result = etree.Element('{%s}modifications' % self._ns, nsmap={'xupdate': self._ns})
+      self._result.set('version', '1.0')
+      if self._testElements(old_root_element, new_root_element):
+        qname = old_root_element.xpath('name()')
+        self._testAttributes(old_root_element, new_root_element, '/%s' % qname)
+        self._compareChildNodes(old_root_element, new_root_element, '/%s' % qname)
+      else:
+        # These XML documents seem to be completely different...
+        if old_root_element.tag != new_root_element.tag:
+          nsmap = old_root_element.nsmap
+          nsmap.update(new_root_element.nsmap)
+          self._xupdateRenameElement(new_root_element.xpath('name()'), '/%s' % old_root_element.xpath('name()'), nsmap)
+        qname = new_root_element.xpath('name()')
+        self._testAttributes(old_root_element, new_root_element, '/%s' % qname)
+        self._compareChildNodes(old_root_element, new_root_element, '/%s' % qname)
+    finally:
+      del old_doc
+      del new_doc
+
+  def output(self, output_file=None):
+    """
+      Output the result of parsing XML documents to 'output_file'.
+      If it is not specified, stdout is assumed.
+    """
+    if output_file is None:
+      output_file = sys.stdout
+    # stream
+    xml = etree.tostring(self._result, encoding='utf-8', pretty_print=True)
+    output_file.write(xml)
+
+  def outputString(self):
+    """
+      Return the result as a string object.
+    """
+    io = StringIO()
+    self.output(io)
+    ret = io.getvalue()
+    io.close()
+    return ret
+
+def main():
+  """
+    The main routine of ERP5Diff.
+  """
+  try:
+    opts, args = getopt.getopt(sys.argv[1:], "ho:v", ["help", "output=", "verbose"])
+  except getopt.GetoptError, msg:
+    print msg
+    print "Try ``erp5diff --help'' for more information."
+    sys.exit(2)
+  output = None
+  verbose = 0
+  for o, a in opts:
+    if o == "-v":
+      verbose = 1
+    elif o in ("-h", "--help"):
+      print '''Usage: erp5diff [OPTION]... OLD_XML NEW_XML
+Make a difference between two XML documents in XUpdate format.
+
+    -h, --help          display this message and exit
+    -o, --output=FILE   output the result to the file FILE
+    -v, --verbose       print verbose messages
+
+Report bugs to <yo at nexedi.com>.'''
+      sys.exit()
+    elif o in ("-o", "--output"):
+      output = a
+
+  if len(args) != 2:
+    if len(args) > 2:
+      print "Too many arguments."
+    else:
+      print "Too few arguments."
+    print "Try ``erp5diff --help'' for more information."
+    sys.exit(2)
+
+  d = ERP5Diff()
+  d.setVerbosity(verbose)
+
+  old_xml = open(args[0])
+  new_xml = open(args[1])
+  d.compare(old_xml, new_xml)
+  old_xml.close()
+  new_xml.close()
+
+  try:
+    if output is not None:
+      file = open(output, 'w')
+    else:
+      file = None
+    d.output(file)
+  except:
+    if output is not None:
+      file.close()
+      os.remove(output)
+    raise
+  else:
+    if file is not None:
+      file.close()
+
+  sys.exit()
+
+if __name__ == '__main__':
+  main()

Added: erp5/trunk/utils/erp5diff/src/ERP5Diff/__init__.py
URL: http://svn.erp5.org/erp5/trunk/utils/erp5diff/src/ERP5Diff/__init__.py?rev=42660&view=auto
==============================================================================
--- erp5/trunk/utils/erp5diff/src/ERP5Diff/__init__.py (added)
+++ erp5/trunk/utils/erp5diff/src/ERP5Diff/__init__.py [utf8] Tue Jan 25 16:11:58 2011
@@ -0,0 +1,5 @@
+# for backward compatibility with old import path
+from ERP5Diff import ERP5Diff as ERP5Diff
+
+
+



More information about the Erp5-report mailing list