[Erp5-report] r29964 - in /erp5/trunk/utils/erp5diff: ./ interfaces/

nobody at svn.erp5.org nobody at svn.erp5.org
Fri Oct 23 11:29:23 CEST 2009


Author: tatuya
Date: Fri Oct 23 11:29:21 2009
New Revision: 29964

URL: http://svn.erp5.org?rev=29964&view=rev
Log:
- replace 4SuiteXML with lxml 
- replace distutill with setuptool 
- append test-cases using docstring
- append buildout setting
- append interface
- eggify

Added:
    erp5/trunk/utils/erp5diff/bootstrap.py
    erp5/trunk/utils/erp5diff/buildout.cfg
    erp5/trunk/utils/erp5diff/interfaces/
    erp5/trunk/utils/erp5diff/interfaces/__init__.py
    erp5/trunk/utils/erp5diff/interfaces/erp5diff.py
    erp5/trunk/utils/erp5diff/setup.cfg
    erp5/trunk/utils/erp5diff/tests.py
Modified:
    erp5/trunk/utils/erp5diff/ERP5Diff.py
    erp5/trunk/utils/erp5diff/README
    erp5/trunk/utils/erp5diff/setup.py

Modified: erp5/trunk/utils/erp5diff/ERP5Diff.py
URL: http://svn.erp5.org/erp5/trunk/utils/erp5diff/ERP5Diff.py?rev=29964&r1=29963&r2=29964&view=diff
==============================================================================
--- erp5/trunk/utils/erp5diff/ERP5Diff.py [utf8] (original)
+++ erp5/trunk/utils/erp5diff/ERP5Diff.py [utf8] Fri Oct 23 11:29:21 2009
@@ -20,19 +20,7 @@
 #
 ##############################################################################
 
-try:
-  from Ft.Xml import Parse as parse
-  from Ft.Xml.Domlette import NonvalidatingReader, PrettyPrint
-  parseString = NonvalidatingReader.parseString
-  from Ft.Xml.Domlette import implementation
-  from Ft.Xml import EMPTY_NAMESPACE
-  def getDOMImplementation():
-    return implementation
-except ImportError:
-  from xml.dom.minidom import parse, parseString
-  from xml.dom.minidom import getDOMImplementation
-  from xml.dom.ext import PrettyPrint
-  EMPTY_NAMESPACE = None
+from lxml import etree
 
 import sys
 import getopt
@@ -40,6 +28,9 @@
 from StringIO import StringIO
 import re
 import codecs
+from copy import deepcopy
+from interfaces.erp5diff import IERP5Diff
+import zope.interface
 
 class ERP5Diff:
   """
@@ -56,6 +47,10 @@
       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,)
+
   def __init__(self):
     """
       Initialize itself.
@@ -84,9 +79,10 @@
     doc_list = []
     for a in args:
       if type(a) == type(''):
-        doc_list.append(parseString(a))
+        doc_list.append(etree.XML(a))
       else:
-        doc_list.append(parse(a))
+        element_tree = etree.parse(a)
+        doc_list.append(element_tree.getroot())
     return doc_list
 
   def _concatPath(self, p1, p2, separator='/'):
@@ -102,151 +98,148 @@
     """
       Return the root element of the result document.
     """
-    return self._result.documentElement
+    return self._result
+    #return self._result.getroottree()
+
+  def _hasChildren(self, element):
+    """
+      Check whether the element has any children
+    """
+    if len(element) == 0:
+      return False
+    return True
 
   def _xupdateAppendAttributes(self, dict, path):
     """
-      Append attributes to the element at 'path'.
-    """
-    root = self._getResultRoot()
-    createElement = self._result.createElementNS
-    createTextNode = self._result.createTextNode
-    append_element = createElement(self._ns, 'xupdate:append')
-    append_element.setAttributeNS(EMPTY_NAMESPACE, 'select', path)
+      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
     for name, val in dict.iteritems():
-      attr_element = createElement(self._ns, 'xupdate:attribute')
-      attr_element.setAttributeNS(EMPTY_NAMESPACE, 'name', name)
-      text_node = createTextNode(val)
-      attr_element.appendChild(text_node)
-      append_element.appendChild(attr_element)
-    root.appendChild(append_element)
+      attr_element = etree.Element('{%s}attribute' % self._ns, nsmap=root.nsmap)
+      attr_element.attrib['name'] = name
+      attr_element.text = val
+      append_element.append(attr_element)
+    root.append(append_element)
 
   def _xupdateRemoveAttribute(self, name, path):
     """
       Remove an attribute from the element at 'path'.
     """
     root = self._getResultRoot()
-    createElement = self._result.createElementNS
-    remove_element = createElement(self._ns, 'xupdate:remove')
-    remove_element.setAttributeNS(EMPTY_NAMESPACE, 'select', self._concatPath(path, 'attribute::' + name))
-    root.appendChild(remove_element)
+    remove_element = etree.Element('{%s}remove' % self._ns, nsmap=root.nsmap)
+    remove_element.attrib['select'] = self._concatPath(path, 'attribute::' + name)
+    root.append(remove_element)
 
   def _xupdateUpdateAttribute(self, name, val, path):
     """
       Update the value of an attribute of the element at 'path'.
     """
     root = self._getResultRoot()
-    createElement = self._result.createElementNS
-    createTextNode = self._result.createTextNode
-    update_element = createElement(self._ns, 'xupdate:update')
-    update_element.setAttributeNS(EMPTY_NAMESPACE, 'select', self._concatPath(path, 'attribute::' + name))
-    text_node = createTextNode(val)
-    update_element.appendChild(text_node)
-    root.appendChild(update_element)
+    update_element = etree.Element('{%s}update' % self._ns, nsmap=root.nsmap)
+    update_element.attrib['select'] = self._concatPath(path, 'attribute::' + name)
+    update_element.text = val
+    root.append(update_element)
 
   def _xupdateRenameElement(self, name, path):
     """
       Rename an existing element at 'path'.
     """
     root = self._getResultRoot()
-    createElement = self._result.createElementNS
-    createTextNode = self._result.createTextNode
-    rename_element = createElement(self._ns, 'xupdate:rename')
-    rename_element.setAttributeNS(EMPTY_NAMESPACE, 'select', path)
-    text_node = createTextNode(name)
-    rename_element.appendChild(text_node)
-    root.appendChild(rename_element)
+    rename_element = etree.Element('{%s}rename' % self._ns, nsmap=root.nsmap)
+    rename_element.attrib['select'] = path
+    rename_element.text = name
+    root.append(rename_element)
 
   def _xupdateUpdateElement(self, element, path):
     """
       Update the contents of an element at 'path' to that of 'element'.
     """
     root = self._getResultRoot()
-    createElement = self._result.createElementNS
-    update_element = createElement(self._ns, 'xupdate:update')
-    update_element.setAttributeNS(EMPTY_NAMESPACE, 'select', path)
-    for node in element.childNodes:
-      #self._p("node is %s" % repr(node))
-      clone_node = node.cloneNode(1)
-      update_element.appendChild(clone_node)
-    root.appendChild(update_element)
+    update_element = etree.Element('{%s}update' % self._ns, nsmap=root.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):
     """
       Remove an element at 'path'.
     """
     root = self._getResultRoot()
-    createElement = self._result.createElementNS
-    remove_element = createElement(self._ns, 'xupdate:remove')
-    remove_element.setAttributeNS(EMPTY_NAMESPACE, 'select', path)
-    root.appendChild(remove_element)
+    remove_element = etree.Element('{%s}remove' % self._ns, nsmap=root.nsmap)
+    remove_element.attrib['select'] = path
+    root.append(remove_element)
 
   def _xupdateInsertBefore(self, element_list, path):
     """
       Insert elements before the element at 'path'.
     """
     root = self._getResultRoot()
-    createElement = self._result.createElementNS
-    createTextNode = self._result.createTextNode
-    insert_element = createElement(self._ns, 'xupdate:insert-before')
-    insert_element.setAttributeNS(EMPTY_NAMESPACE, 'select', path)
+    insert_element = etree.Element('{%s}insert-before' % self._ns, nsmap=root.nsmap)
+    insert_element.attrib['select'] = path
     for element in element_list:
-      child_element = createElement(self._ns, 'xupdate:element')
-      child_element.setAttributeNS(EMPTY_NAMESPACE, 'name', element.tagName)
-      attr_map = element.attributes
-      for attr in attr_map.values():
-        attr_element = createElement(self._ns, 'xupdate:attribute')
-        attr_element.setAttributeNS(EMPTY_NAMESPACE, 'name', attr.name)
-        text_node = createTextNode(attr.nodeValue)
-        attr_element.appendChild(text_node)
-        child_element.appendChild(attr_element)
-      for child in element.childNodes:
-        clone_node = child.cloneNode(1)
-        child_element.appendChild(clone_node)
-      insert_element.appendChild(child_element)
-    root.appendChild(insert_element)
+      child_element = etree.Element('{%s}element' % self._ns, nsmap=root.nsmap)
+      child_element.attrib['name'] = element.tag
+      attr_map = element.attrib
+      for name, value in attr_map.items():
+        attr_element = etree.Element('{%s}attribute' % self._ns, nsmap=root.nsmap)
+        attr_element.attrib['name'] = name
+        attr_element.text = value
+        child_element.append(attr_element)
+      for child in element:
+        clone_node = deepcopy(child)
+        child_element.append(clone_node)
+      if self._hasChildren(child_element):
+        child_element[-1].tail = element.text
+      insert_element.append(child_element)
+    root.append(insert_element)
 
   def _xupdateAppendElements(self, element_list, path):
     """
       Append elements to the element at 'path'.
     """
     root = self._getResultRoot()
-    createElement = self._result.createElementNS
-    createTextNode = self._result.createTextNode
-    append_element = createElement(self._ns, 'xupdate:append')
-    append_element.setAttributeNS(EMPTY_NAMESPACE, 'select', path)
+    append_element = etree.Element('{%s}append' % self._ns, nsmap=root.nsmap)
+    append_element.attrib['select'] = path
     for element in element_list:
-      child_element = createElement(self._ns, 'xupdate:element')
-      child_element.setAttributeNS(EMPTY_NAMESPACE, 'name', element.tagName)
-      attr_map = element.attributes
-      for attr in attr_map.values():
-        attr_element = createElement(self._ns, 'xupdate:attribute')
-        attr_element.setAttributeNS(EMPTY_NAMESPACE, 'name', attr.name)
-        text_node = createTextNode(attr.nodeValue)
-        attr_element.appendChild(text_node)
-        child_element.appendChild(attr_element)
-      for child in element.childNodes:
-        clone_node = child.cloneNode(1)
-        child_element.appendChild(clone_node)
-      append_element.appendChild(child_element)
-    root.appendChild(append_element)
+      child_element = etree.Element('{%s}element' % self._ns, nsmap=root.nsmap)
+      child_element.attrib['name'] = element.tag
+      attr_map = element.attrib
+      for name, value in attr_map.items():
+        attr_element = etree.Element('{%s}attribute' % self._ns, nsmap=root.nsmap)
+        attr_element.attrib['name'] = name
+        attr_element.text = value
+        child_element.append(attr_element)
+      for child in element:
+        clone_node = deepcopy(child)
+        child_element.append(clone_node)
+      if self._hasChildren(child_element):
+        child_element[-1].tail = element.text
+      append_element.append(child_element)
+    root.append(append_element)
 
   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 element1.nodeType != element2.nodeType or element1.nodeType != element1.ELEMENT_NODE:
+    if type(element1) !=  type(element2) or type(element1) != etree._Element:
       return 0
 
-    if element1.tagName != element2.tagName:
+    if element1.tag != element2.tag:
       return 0
 
     id_list = []
-    for attr_map in (element1.attributes, element2.attributes):
-      for attr in attr_map.values():
-        if attr.name == 'id':
-          id_list.append(attr.nodeValue)
+    for attr_map in (element1.attrib, element2.attrib):
+      for name, value in attr_map.items():
+        if name == 'id':
+          id_list.append(value)
           break
 
     if len(id_list) == 0:
@@ -257,18 +250,18 @@
 
   def _testAttributes(self, element1, element2, path):
     """
-      Test attributes of two given elements. Add differences, if any.
+      Test attrib of two given elements. Add differences, if any.
     """
     # Make a list of dictionaries of the attributes.
     dict_list = []
-    for attr_map in (element1.attributes, element2.attributes):
-      dict = {}
-      for attr in attr_map.values():
-        dict[attr.name] = attr.nodeValue
-      dict_list.append(dict)
+    for attr_map in (element1.attrib, element2.attrib):
+      d = {}
+      for name, value in attr_map.items():
+        d[name] = value
+      dict_list.append(d)
     dict1, dict2 = dict_list
 
-    # Find all added or removed or changed attributes.
+    # Find all added or removed or changed attrib.
     for name1, val1 in dict1.iteritems():
       if name1 in dict2:
         if val1 != dict2[name1]:
@@ -279,21 +272,23 @@
       else:
         # This attribute is removed.
         self._xupdateRemoveAttribute(name1, path)
-    dict = {}
+    d = {}
     for name2, val2 in dict2.iteritems():
       if val2 is not None:
         # This attribute is added.
-        dict[name2] = val2
-    if dict != {}:
-      self._xupdateAppendAttributes(dict, path)
+        d[name2] = val2
+    if d != {}:
+      self._xupdateAppendAttributes(d, path)
 
   def _checkEmptiness(self, element):
     """
       Check if an element has child values.
     """
-    for child in element.childNodes:
-      if child.nodeType == child.ELEMENT_NODE or child.nodeType == child.TEXT_NODE:
+    for child in element:
+      if type(child) == etree._Element:
         return 0
+    if element.text is not None:
+      return 0
     return 1
 
   def _checkIgnoreText(self, element):
@@ -301,8 +296,8 @@
       Determine if text should be ignored by heuristics,
       because ERP5 does not define any schema at the moment.
     """
-    for child in element.childNodes:
-      if child.nodeType == child.ELEMENT_NODE:
+    for child in element:
+      if type(child) == etree._Element:
         return 1
     return 0
 
@@ -313,33 +308,33 @@
     num_map = {}
     count_map = {}
     for element in element_list:
-      if element.tagName in num_map:
-        num_map[element.tagName] += 1
+      if element.tag in num_map:
+        num_map[element.tag] += 1
       else:
-        num_map[element.tagName] = 1
-        count_map[element.tagName] = 0
+        num_map[element.tag] = 1
+        count_map[element.tag] = 0
 
     path_list = []
     for element in element_list:
-      # Check if this element has an attribute 'id'.
+      # Check if this element has an attribute 'id'.s
       id_val = None
-      attr_map = element.attributes
-      for attr in attr_map.values():
-        if attr.name == 'id':
-          id_val = attr.nodeValue
+      attr_map = element.attrib
+      for name, value in attr_map.items():
+        if name == 'id':
+          id_val = value
           break
 
       if id_val is not None:
         # If an attribute 'id' is present, uses the attribute for convenience.
-        path_list.append("%s[@id='%s']" % (element.tagName, id_val))
+        path_list.append("%s[@id='%s']" % (element.tag, id_val))
         # Increase the count, for a case where other elements with the same tag name do not have
-        # 'id' attributes.
-        count_map[element.tagName] += 1
-      elif num_map[element.tagName] > 1:
-        path_list.append('%s[%d]' % (element.tagName, count_map[element.tagName]))
-        count_map[element.tagName] += 1
+        # 'id' attrib.
+        count_map[element.tag] += 1
+      elif num_map[element.tag] > 1:
+        path_list.append('%s[%d]' % (element.tag, count_map[element.tag]))
+        count_map[element.tag] += 1
       else:
-        path_list.append(element.tagName)
+        path_list.append(element.tag)
 
     return path_list
 
@@ -348,8 +343,8 @@
       Aggregate child elements of an element into a list.
     """
     element_list = []
-    for child in element.childNodes:
-      if child.nodeType == child.ELEMENT_NODE:
+    for child in element:
+      if type(child) == etree._Element:
         element_list.append(child)
     return element_list
 
@@ -358,9 +353,11 @@
       Aggregate child text nodes of an element into a single string.
     """
     text = ''
-    for child in element.childNodes:
-      if child.nodeType == child.TEXT_NODE:
-        text += child.nodeValue
+    if not self._hasChildren(element):
+      return element.text
+    for child in element:
+      if type(child) == etree._Element:
+        text += child.text
     return text
 
   def _compareChildNodes(self, old_element, new_element, path):
@@ -433,40 +430,36 @@
       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.documentElement
-    new_root_element = new_doc.documentElement
+    old_root_element = old_doc #.getroottree() #old_doc.documentElement
+    new_root_element = new_doc #.getroottree() #new_doc.documentElement
     try:
-      impl = getDOMImplementation()
-      # XXX this namespace argument won't be handled correctly in minidom.
-      # XXX So work around that problem when outputting the result.
       if self._result is not None:
-        self._result.close()
-      self._result = impl.createDocument(self._ns, 'xupdate:modifications', None)
-      attr_version = self._result.createAttributeNS(EMPTY_NAMESPACE, 'version')
-      attr_version.value = '1.0'
-      self._result.documentElement.setAttributeNodeNS(attr_version)
+        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):
         self._testAttributes(old_root_element, new_root_element, '/')
         self._compareChildNodes(old_root_element, new_root_element, '/')
       else:
         # These XML documents seem to be completely different...
-        if old_root_element.tagName != new_root_element.tagName:
-          self._xupdateRenameElement(new_root_element.tagName, '/')
+        if old_root_element.tag != new_root_element.tag:
+          self._xupdateRenameElement(new_root_element.tag, '/')
         self._testAttributes(old_root_element, new_root_element, '/')
         self._xupdateUpdateElement(new_root_element, '/')
     finally:
       del old_doc
       del new_doc
 
-  def output(self, file=None):
-    """
-      Output the result of parsing XML documents to 'file'.
+  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 file is None:
-      file = sys.stdout
-
-    PrettyPrint(self._result.documentElement, stream=file, encoding='UTF-8')
+    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):
     """

Modified: erp5/trunk/utils/erp5diff/README
URL: http://svn.erp5.org/erp5/trunk/utils/erp5diff/README?rev=29964&r1=29963&r2=29964&view=diff
==============================================================================
--- erp5/trunk/utils/erp5diff/README [utf8] (original)
+++ erp5/trunk/utils/erp5diff/README [utf8] Fri Oct 23 11:29:21 2009
@@ -1,6 +1,6 @@
 This is a XUpdate Generator for ERP5.
 
-See <http://www.xmldb.org/xupdate/index.html> for information on
+See <http://xmldb-org.sourceforge.net/xupdate/> for information on
 XUpdate.
 
 See <http://erp5.org/> for information on ERP5.
@@ -14,4 +14,673 @@
 Also, you can use the module ERP5Diff from your Python script.
 Do "pydoc ERP5Diff" for more information.
 
+
+ERP5Diff Usage and its output example
+=====================================
+
+1. update the texts of the three elements
+
+  >>> from ERP5Diff import ERP5Diff                                                                                                  
+  >>> erp5diff = ERP5Diff()
+
+  >>> old_xml = """
+  ... <erp5>       
+  ...   <object portal_type="Person" id="313730">     
+  ...     <description type="text">description1 --- $sdfr&#231;_sdfs&#231;df_oisfsopf</description>
+  ...     <first_name type="string">Kamada</first_name>                                            
+  ...     <last_name type="string">Kamada</last_name>                                              
+  ...     <workflow_action id="edit_workflow">                                                     
+  ...       <time type="date">2009/08/28 19:12:24.700 GMT+9</time>                                 
+  ...     </workflow_action>                                                                       
+  ...     </object>                                                                                
+  ... </erp5>                                                                                      
+  ... """                                                                                          
+  >>> new_xml = """                                                                                
+  ... <erp5>                                                                                       
+  ...   <object portal_type="Person" id="313730">                                                                                                                                                       
+  ...     <description type="text">description3 &#231;sdf__sdf&#231;&#231;&#231;_df___&amp;amp;&amp;amp;&#233;]]]&#176;&#176;&#176;&#176;&#176;&#176;</description>                                                                                         
+  ...     <first_name type="string">Tatuya</first_name>                                                                        
+  ...     <last_name type="string">Kamada</last_name>                                                                          
+  ...     <workflow_action id="edit_workflow">                                                                                 
+  ...       <time type="date">2009/08/28 19:12:24.703 GMT+9</time>                                                             
+  ...     </workflow_action>                                                                                                   
+  ...     </object>                                                                                                            
+  ... </erp5>                                                                                                                  
+  ... """                    
+
+  >>> erp5diff.compare(old_xml, new_xml)
+  >>> erp5diff.output()
+  <xupdate:modifications xmlns:xupdate="http://www.xmldb.org/xupdate" version="1.0">
+    <xupdate:update select="/object[@id='313730']/description">description3 çsdf__sdfççç_df___&amp;amp;&amp;amp;é]]]°°°°°°</xupdate:update>
+    <xupdate:update select="/object[@id='313730']/first_name">Tatuya</xupdate:update>
+    <xupdate:update select="/object[@id='313730']/workflow_action[@id='edit_workflow']/time">2009/08/28 19:12:24.703 GMT+9</xupdate:update>
+  </xupdate:modifications>
+
+2. update one element
+
+  >>> old_xml = """
+  ... <erp5>
+  ...   <object portal_type="Person" id="313730">
+  ...      <description type="text">description2&#233;&#224;@  $*&amp;lt; &amp;lt; -----</description>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> new_xml = """
+  ... <erp5>
+  ...   <object portal_type="Person" id="313730">
+  ...      <description type="text">description3&#233;&#224;@  $*&amp;lt; &amp;lt; -----</description>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> erp5diff.compare(old_xml, new_xml)
+  >>> erp5diff.output()
+  <xupdate:modifications xmlns:xupdate="http://www.xmldb.org/xupdate" version="1.0">
+    <xupdate:update select="/object[@id='313730']/description">description3éà@  $*&amp;lt; &amp;lt; -----</xupdate:update>
+  </xupdate:modifications>
+
+3. same
+
+  >>> old_xml = """
+  ... <erp5>       
+  ...   <object portal_type="Person" id="313730">
+  ...     <title type="string">Tatuya Kamada</title>
+  ...     <subject_list type="lines">&lt;?xml version="1.0"?&gt;&lt;marshal&gt;&lt;list id="i2"&gt;&lt;/list&gt;&lt;/marshal&gt;</subject_list>
+  ...     <first_name type="string">Kamada</first_name>
+  ...     <last_name type="string">Tatuya</last_name>
+  ...     <workflow_action id="edit_workflow">
+  ...       <actor type="string">tatuya</actor>
+  ...       <time type="date">2009/08/28 19:12:26.631 GMT+9</time>
+  ...     </workflow_action>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> new_xml = """
+  ... <erp5>
+  ...   <object portal_type="Person" id="313730">
+  ...     <title type="string">Tatuya Kamada</title>
+  ...     <subject_list type="lines">&lt;?xml version="1.0"?&gt;&lt;marshal&gt;&lt;list id="i2"&gt;&lt;/list&gt;&lt;/marshal&gt;</subject_list>
+  ...     <first_name type="string">Kamada</first_name>
+  ...     <last_name type="string">Tatuya</last_name>
+  ...     <workflow_action id="edit_workflow">
+  ...       <actor type="string">tatuya</actor>
+  ...       <time type="date">2009/08/28 19:12:26.631 GMT+9</time>
+  ...     </workflow_action>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> erp5diff.compare(old_xml, new_xml)
+  >>> erp5diff.output()
+  <xupdate:modifications xmlns:xupdate="http://www.xmldb.org/xupdate" version="1.0"/>
+
+4. update the texts of the elements and remove an element
+
+  >>> old_xml = """                                                                                                       
+  ... <erp5>                                                                                                              
+  ...   <object portal_type="Person" id="313730">
+  ...     <description type="text">description2&#233;&#224;@  $*&amp;lt; &amp;lt;&amp;lt;&amp;lt;  -----</description>
+  ...     <language type="string">en</language>
+  ...     <workflow_action id="edit_workflow">
+  ...       <time type="date">2009/08/28 19:12:34.424 GMT+9</time>
+  ...     </workflow_action>
+  ...     <workflow_action id="edit_workflow">
+  ...       <time type="date">2009/08/28 19:12:34.432 GMT+9</time>
+  ...     </workflow_action>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> new_xml = """
+  ... <erp5>
+  ...   <object portal_type="Person" id="313730">
+  ...     <description type="text">description1 --- $sdfr&#231;_sdfs&#231;df_oisfsopf</description>
+  ...     <language type="None"/>
+  ...     <workflow_action id="edit_workflow">
+  ...       <time type="date">2009/08/28 19:12:34.424 GMT+9</time>
+  ...     </workflow_action>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> erp5diff.compare(old_xml, new_xml)
+  >>> erp5diff.output()
+  <xupdate:modifications xmlns:xupdate="http://www.xmldb.org/xupdate" version="1.0">
+    <xupdate:update select="/object[@id='313730']/description">description1 --- $sdfrç_sdfsçdf_oisfsopf</xupdate:update>
+    <xupdate:update select="/object[@id='313730']/language/attribute::type">None</xupdate:update>
+    <xupdate:update select="/object[@id='313730']/language"/>
+    <xupdate:remove select="/object[@id='313730']/workflow_action[@id='edit_workflow']"/>
+  </xupdate:modifications>
+
+5. update two elements includes some symbols 
+
+  >>> old_xml = """
+  ... <erp5>
+  ...   <object portal_type="Person" id="313730">
+  ...     <description type="text">description2&#233;&#224;@  $*&amp;lt;&amp;lt;-----&amp;gt;&amp;gt;</description>
+  ...     <language type="string">jp</language>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> new_xml = """
+  ... <erp5>
+  ...   <object portal_type="Person" id="313730">
+  ...     <description type="text">description4 sdflkmooo^^^^]]]]]{{{{{{{</description>
+  ...     <language type="string">ca</language>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> erp5diff.compare(old_xml, new_xml)
+  >>> erp5diff.output()
+  <xupdate:modifications xmlns:xupdate="http://www.xmldb.org/xupdate" version="1.0">
+    <xupdate:update select="/object[@id='313730']/description">description4 sdflkmooo^^^^]]]]]{{{{{{{</xupdate:update>
+    <xupdate:update select="/object[@id='313730']/language">ca</xupdate:update>
+  </xupdate:modifications>
+
+6. update two date element which have same id
+
+  >>> old_xml = """                                                                                                      
+  ... <erp5>                                                                                                             
+  ...   <object portal_type="Person" id="313730">                                                                        
+  ...     <workflow_action id="edit_workflow">                                                                           
+  ...       <time type="date">2009/08/28 19:12:40.550 GMT+9</time>                                                       
+  ...     </workflow_action>
+  ...     <workflow_action id="edit_workflow">
+  ...       <time type="date">2009/08/28 19:12:40.903 GMT+9</time>
+  ...     </workflow_action>
+  ...     <workflow_action id="edit_workflow">
+  ...       <time type="date">2009/08/28 19:12:40.907 GMT+9</time>
+  ...     </workflow_action>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> new_xml = """
+  ... <erp5>
+  ...   <object portal_type="Person" id="313730">
+  ...     <workflow_action id="edit_workflow">
+  ...       <time type="date">2009/08/28 19:12:40.550 GMT+9</time>
+  ...     </workflow_action>
+  ...     <workflow_action id="edit_workflow">
+  ...       <time type="date">2009/08/28 19:12:40.905 GMT+9</time>
+  ...     </workflow_action>
+  ...     <workflow_action id="edit_workflow">
+  ...       <time type="date">2009/08/28 19:12:40.910 GMT+9</time>
+  ...     </workflow_action>
+  ...    </object>
+  ... </erp5>
+  ... """
+  >>> erp5diff.compare(old_xml, new_xml)
+  >>> erp5diff.output()
+  <xupdate:modifications xmlns:xupdate="http://www.xmldb.org/xupdate" version="1.0">
+    <xupdate:update select="/object[@id='313730']/workflow_action[@id='edit_workflow']/time">2009/08/28 19:12:40.905 GMT+9</xupdate:update>
+    <xupdate:update select="/object[@id='313730']/workflow_action[@id='edit_workflow']/time">2009/08/28 19:12:40.910 GMT+9</xupdate:update>
+  </xupdate:modifications>
+
+7. insert and remove elements
+
+  >>> old_xml = """
+  ... <erp5>
+  ...   <object portal_type="Person" id="313731">
+  ...     <local_role type="tokens" id="tk">&lt;?xml version="1.0"?&gt;&lt;marshal&gt;&lt;tuple&gt;&lt;string&gt;Manager&lt;/string&gt;&lt;string&gt;Owner&lt;/string&gt;&lt;/tuple&gt;&lt;/marshal&gt;</local_role>
+  ...     <local_permission type="tokens" id="Access contents information">&lt;?xml version="1.0"?&gt;</local_permission>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> new_xml = """
+  ... <erp5>
+  ...   <object portal_type="Person" id="313731">
+  ...     <local_role type="tokens" id="tatuya">&lt;?xml version="1.0"?&gt;&lt;marshal&gt;&lt;tuple&gt;&lt;string&gt;Owner&lt;/string&gt;&lt;/tuple&gt;&lt;/marshal&gt;</local_role>
+  ...     <local_permission type="tokens" id="Access contents information">&lt;?xml version="1.0"?&gt;</local_permission>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> erp5diff.compare(old_xml, new_xml)
+  >>> erp5diff.output()
+  <xupdate:modifications xmlns:xupdate="http://www.xmldb.org/xupdate" version="1.0">
+    <xupdate:remove select="/object[@id='313731']/local_role[@id='tk']"/>
+    <xupdate:insert-before select="/object[@id='313731']/local_permission[@id='Access contents information']">
+      <xupdate:element name="local_role"><xupdate:attribute name="type">tokens</xupdate:attribute><xupdate:attribute name="id">tatuya</xupdate:attribute>&lt;?xml version="1.0"?&gt;&lt;marshal&gt;&lt;tuple&gt;&lt;string&gt;Owner&lt;/string&gt;&lt;/tuple&gt;&lt;/marshal&gt;</xupdate:element>
+    </xupdate:insert-before>
+  </xupdate:modifications>
+
+8. update xml in xml
+
+  >>> old_xml = """
+  ... <erp5>
+  ...   <object portal_type="Person" id="313731">
+  ...     <local_permission type="tokens" id="View">&lt;?xml version="1.0"?&gt;&lt;marshal&gt;&lt;tuple&gt;&lt;string&gt;Manager&lt;/string&gt;&lt;string&gt;Owner&lt;/string&gt;&lt;/tuple&gt;&lt;/marshal&gt;</local_permission>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> new_xml = """
+  ... <erp5>
+  ...   <object portal_type="Person" id="313731">
+  ...     <local_permission type="tokens" id="View">&lt;?xml version="1.0"?&gt;&lt;marshal&gt;&lt;tuple&gt;&lt;string&gt;Assignee&lt;/string&gt;&lt;string&gt;Assignor&lt;/string&gt;&lt;string&gt;Associate&lt;/string&gt;&lt;string&gt;Auditor&lt;/string&gt;&lt;string&gt;Author&lt;/string&gt;&lt;string&gt;Manager&lt;/string&gt;&lt;string&gt;Owner&lt;/string&gt;&lt;/tuple&gt;&lt;/marshal&gt;</local_permission>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> erp5diff.compare(old_xml, new_xml)
+  >>> erp5diff.output()
+  <xupdate:modifications xmlns:xupdate="http://www.xmldb.org/xupdate" version="1.0">
+    <xupdate:update select="/object[@id='313731']/local_permission[@id='View']">&lt;?xml version="1.0"?&gt;&lt;marshal&gt;&lt;tuple&gt;&lt;string&gt;Assignee&lt;/string&gt;&lt;string&gt;Assignor&lt;/string&gt;&lt;string&gt;Associate&lt;/string&gt;&lt;string&gt;Auditor&lt;/string&gt;&lt;string&gt;Author&lt;/string&gt;&lt;string&gt;Manager&lt;/string&gt;&lt;string&gt;Owner&lt;/string&gt;&lt;/tuple&gt;&lt;/marshal&gt;</xupdate:update>
+  </xupdate:modifications>
+
+9. rename element
+
+  >>> old_xml = """
+  ... <erp5>
+  ...   <object portal_type="Person" id="313730">
+  ...     <first_name type="string">Tatuya</first_name>
+  ...     <last_name type="string">Kamada</last_name>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> new_xml = """
+  ... <erp5>
+  ...   <object portal_type="Person" id="313730">
+  ...     <given_name type="string">Tatuya</given_name>
+  ...     <family_name type="string">Kamada</family_name>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> erp5diff.compare(old_xml, new_xml)
+  >>> erp5diff.output()
+  <xupdate:modifications xmlns:xupdate="http://www.xmldb.org/xupdate" version="1.0">
+    <xupdate:remove select="/object[@id='313730']/first_name"/>
+    <xupdate:remove select="/object[@id='313730']/last_name"/>
+    <xupdate:append select="/object[@id='313730']">
+      <xupdate:element name="given_name"><xupdate:attribute name="type">string</xupdate:attribute>Tatuya</xupdate:element>
+      <xupdate:element name="family_name"><xupdate:attribute name="type">string</xupdate:attribute>Kamada</xupdate:element>
+    </xupdate:append>
+  </xupdate:modifications>
+
+10. rename root element
+
+>>> old_xml = """
+... <erp5>       
+...   <object portal_type="Person" id="313730">
+...     <id type="string">313730</id>
+...     <title type="string">Tatuya Kamada</title>
+...   </object>
+... </erp5>
+... """
+>>> new_xml = """
+... <erp6>
+...   <object portal_type="Person" id="313730">
+...     <id type="string">313730</id>
+...     <title type="string">Tatuya Kamada</title>
+...   </object>
+... </erp6>
+... """
+>>> erp5diff.compare(old_xml, new_xml)
+>>> erp5diff.output()
+<xupdate:modifications xmlns:xupdate="http://www.xmldb.org/xupdate" version="1.0">
+  <xupdate:rename select="/">erp6</xupdate:rename>
+  <xupdate:update select="/"><object portal_type="Person" id="313730">
+    <id type="string">313730</id>
+    <title type="string">Tatuya Kamada</title>
+  </object>
+</xupdate:update>
+</xupdate:modifications>
+
+
+11. Update one attribute
+
+>>> old_xml = """
+... <erp5>
+...   <object portal_type="Person" id="313730">
+...     <local_role type="tokens" id="fab">&lt;?xml version="1.0"?&gt;&lt;marshal&gt;&lt;tuple&gt;&lt;string&gt;Owner&lt;/string&gt;&lt;/tuple&gt;&lt;/marshal&gt;</local_role>
+...   </object>
+... </erp5>
+... """
+>>> new_xml = """
+... <erp5>
+...   <object portal_type="Person" id="313730">
+...     <local_role type="ccc" id="fab">&lt;?xml version="1.0"?&gt;&lt;marshal&gt;&lt;tuple&gt;&lt;string&gt;Owner&lt;/string&gt;&lt;/tuple&gt;&lt;/marshal&gt;</local_role>
+...   </object>
+... </erp5>
+... """
+>>> erp5diff.compare(old_xml, new_xml)
+>>> erp5diff.output()
+<xupdate:modifications xmlns:xupdate="http://www.xmldb.org/xupdate" version="1.0">
+  <xupdate:update select="/object[@id='313730']/local_role[@id='fab']/attribute::type">ccc</xupdate:update>
+</xupdate:modifications>
+
+12. Update two attribute 
+
+  >>> old_xml = """
+  ... <erp5>
+  ...   <object portal_type="Person" id="313730">
+  ...     <local_permission attr_a='aaa' type="tokens" id="View">&lt;?xml version="1.0"?&gt;&lt;marshal&gt;&lt;tuple&gt;&lt;string&gt;Assignee&lt;/string&gt;&lt;string&gt;Assignor&lt;/string&gt;&lt;string&gt;Associate&lt;/string&gt;&lt;string&gt;Auditor&lt;/string&gt;&lt;string&gt;Author&lt;/string&gt;&lt;string&gt;Manager&lt;/string&gt;&lt;string&gt;Owner&lt;/string&gt;&lt;/tuple&gt;&lt;/marshal&gt;</local_permission>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> new_xml = """
+  ... <erp5>
+  ...   <object portal_type="Person" id="313730">
+  ...     <local_permission attr_a='ccc' type="ccc" id="View">&lt;?xml version="1.0"?&gt;&lt;marshal&gt;&lt;tuple&gt;&lt;string&gt;Assignee&lt;/string&gt;&lt;string&gt;Assignor&lt;/string&gt;&lt;string&gt;Associate&lt;/string&gt;&lt;string&gt;Auditor&lt;/string&gt;&lt;string&gt;Author&lt;/string&gt;&lt;string&gt;Manager&lt;/string&gt;&lt;string&gt;Owner&lt;/string&gt;&lt;/tuple&gt;&lt;/marshal&gt;</local_permission>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> erp5diff.compare(old_xml, new_xml)
+  >>> erp5diff.output()
+  <xupdate:modifications xmlns:xupdate="http://www.xmldb.org/xupdate" version="1.0">
+    <xupdate:update select="/object[@id='313730']/local_permission[@id='View']/attribute::attr_a">ccc</xupdate:update>
+    <xupdate:update select="/object[@id='313730']/local_permission[@id='View']/attribute::type">ccc</xupdate:update>
+  </xupdate:modifications>
+
+13. Update three attribute
+
+  >>> old_xml = """
+  ... <erp5>
+  ...   <object portal_type="Person" id="313730">
+  ...     <title attribute_a="aaa" attribute_b="bbb" attribute_c="ccc" type="string">Tatuya Kamada</title>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> new_xml = """
+  ... <erp5>
+  ...   <object portal_type="Person" id="313730">
+  ...     <title attribute_a="nnn" attribute_b="nnn" attribute_c="nnn" type="string">Tatuya Kamada</title>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> erp5diff.compare(old_xml, new_xml)
+  >>> erp5diff.output()
+  <xupdate:modifications xmlns:xupdate="http://www.xmldb.org/xupdate" version="1.0">
+    <xupdate:update select="/object[@id='313730']/title/attribute::attribute_b">nnn</xupdate:update>
+    <xupdate:update select="/object[@id='313730']/title/attribute::attribute_c">nnn</xupdate:update>
+    <xupdate:update select="/object[@id='313730']/title/attribute::attribute_a">nnn</xupdate:update>
+  </xupdate:modifications>
+
+14. Remove one attribute
+
+  >>> old_xml = """
+  ... <erp5>
+  ...   <object portal_type="Person" id="313730">
+  ...     <first_name attribute_a="aaa" attribute_b="bbb" attribute_c="ccc" type="string">Tatuya</first_name>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> new_xml = """
+  ... <erp5>
+  ...   <object portal_type="Person" id="313730">
+  ...     <first_name attribute_a="aaa" attribute_b="bbb" type="string">Tatuya</first_name>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> erp5diff.compare(old_xml, new_xml)
+  >>> erp5diff.output()
+  <xupdate:modifications xmlns:xupdate="http://www.xmldb.org/xupdate" version="1.0">
+    <xupdate:remove select="/object[@id='313730']/first_name/attribute::attribute_c"/>
+  </xupdate:modifications>
+
+15. Remove two attribute
+
+  >>> old_xml = """
+  ... <erp5>
+  ...   <object portal_type="Person" id="313730">
+  ...     <first_name attribute_a="aaa" attribute_b="bbb" attribute_c="ccc" type="string">Tatuya</first_name>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> new_xml = """
+  ... <erp5>
+  ...   <object portal_type="Person" id="313730">
+  ...     <first_name attribute_a="aaa" type="string">Tatuya</first_name>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> erp5diff.compare(old_xml, new_xml)
+  >>> erp5diff.output()
+  <xupdate:modifications xmlns:xupdate="http://www.xmldb.org/xupdate" version="1.0">
+    <xupdate:remove select="/object[@id='313730']/first_name/attribute::attribute_b"/>
+    <xupdate:remove select="/object[@id='313730']/first_name/attribute::attribute_c"/>
+  </xupdate:modifications>
+
+
+16. Remove three attribute
+
+  >>> old_xml = """
+  ... <erp5>
+  ...   <object portal_type="Person" id="313730">
+  ...     <first_name attribute_a="aaa" attribute_b="bbb" attribute_c="ccc" type="string">Tatuya</first_name>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> new_xml = """
+  ... <erp5>
+  ...   <object portal_type="Person" id="313730">
+  ...     <first_name type="string">Tatuya</first_name>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> erp5diff.compare(old_xml, new_xml)
+  >>> erp5diff.output()
+  <xupdate:modifications xmlns:xupdate="http://www.xmldb.org/xupdate" version="1.0">
+    <xupdate:remove select="/object[@id='313730']/first_name/attribute::attribute_b"/>
+    <xupdate:remove select="/object[@id='313730']/first_name/attribute::attribute_c"/>
+    <xupdate:remove select="/object[@id='313730']/first_name/attribute::attribute_a"/>
+  </xupdate:modifications>
+
+17. Append one attribute
+
+  >>> old_xml = """
+  ... <erp5>
+  ...   <object portal_type="Person" id="313730">
+  ...     <last_name type="string">Kamada</last_name>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> new_xml = """
+  ... <erp5>
+  ...   <object portal_type="Person" id="313730">
+  ...     <last_name attribute_a="aaa" type="string">Kamada</last_name>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> erp5diff.compare(old_xml, new_xml)
+  >>> erp5diff.output()
+  <xupdate:modifications xmlns:xupdate="http://www.xmldb.org/xupdate" version="1.0">
+    <xupdate:append select="/object[@id='313730']/last_name">
+      <xupdate:attribute name="attribute_a">aaa</xupdate:attribute>
+    </xupdate:append>
+  </xupdate:modifications>
+
+
+18. Append two attribute
+
+  >>> old_xml = """
+  ... <erp5>
+  ...   <object portal_type="Person" id="313730">
+  ...     <last_name type="string">Kamada</last_name>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> new_xml = """
+  ... <erp5>
+  ...   <object portal_type="Person" id="313730">
+  ...     <last_name attribute_a="aaa" attribute_b="bbb" type="string">Kamada</last_name>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> erp5diff.compare(old_xml, new_xml)
+  >>> erp5diff.output()
+  <xupdate:modifications xmlns:xupdate="http://www.xmldb.org/xupdate" version="1.0">
+    <xupdate:append select="/object[@id='313730']/last_name">
+      <xupdate:attribute name="attribute_b">bbb</xupdate:attribute>
+      <xupdate:attribute name="attribute_a">aaa</xupdate:attribute>
+    </xupdate:append>
+  </xupdate:modifications>
+
+19. Append three attribute
+
+  >>> old_xml = """
+  ... <erp5>
+  ...   <object portal_type="Person" id="313730">
+  ...     <last_name type="string">Kamada</last_name>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> new_xml = """
+  ... <erp5>
+  ...   <object portal_type="Person" id="313730">
+  ...     <last_name attribute_a="aaa" attribute_b="bbb" attribute_c="ccc" type="string">Kamada</last_name>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> erp5diff.compare(old_xml, new_xml)
+  >>> erp5diff.output()
+  <xupdate:modifications xmlns:xupdate="http://www.xmldb.org/xupdate" version="1.0">
+    <xupdate:append select="/object[@id='313730']/last_name">
+      <xupdate:attribute name="attribute_b">bbb</xupdate:attribute>
+      <xupdate:attribute name="attribute_c">ccc</xupdate:attribute>
+      <xupdate:attribute name="attribute_a">aaa</xupdate:attribute>
+    </xupdate:append>
+  </xupdate:modifications>
+
+
+20. Remove some elements that have same id
+
+This is an unexpected case for current ERP5Diff alogrithm. So current ERP5Diff 
+does not work as bellow example. This is a known bug.
+
+  >>> old_xml = """                                                                                                       
+  ... <erp5>                                                                                                              
+  ...   <object portal_type="Person" id="313730">
+  ...     <workflow_action id="edit_workflow">
+  ...       <time type="date">2009/08/28 19:12:34.424 GMT+9</time>
+  ...     </workflow_action>
+  ...     <workflow_action id="edit_workflow">
+  ...       <time type="date">2009/08/28 19:12:34.432 GMT+9</time>
+  ...     </workflow_action>
+  ...     <workflow_action id="edit_workflow">
+  ...       <time type="date">2009/08/28 19:12:34.434 GMT+9</time>
+  ...     </workflow_action>
+  ...     <workflow_action id="edit_workflow">
+  ...       <time type="date">2009/08/28 19:12:34.432 GMT+9</time>
+  ...     </workflow_action>
+  ...     <workflow_action id="edit_workflow">
+  ...       <time type="date">2009/08/28 19:12:34.430 GMT+9</time>
+  ...     </workflow_action>
+  ...     <workflow_action id="edit_workflow">
+  ...       <time type="date">2009/08/28 19:12:34.428 GMT+9</time>
+  ...     </workflow_action>
+  ...     <workflow_action id="edit_workflow">
+  ...       <time type="date">2009/08/28 19:12:34.426 GMT+9</time>
+  ...     </workflow_action>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> new_xml = """
+  ... <erp5>
+  ...   <object portal_type="Person" id="313730">
+  ...     <workflow_action id="edit_workflow">
+  ...       <time type="date">2009/08/28 19:12:34.424 GMT+9</time>
+  ...     </workflow_action>
+  ...     <workflow_action id="edit_workflow">
+  ...       <time type="date">2009/08/28 19:12:34.430 GMT+9</time>
+  ...     </workflow_action>
+  ...     <workflow_action id="edit_workflow">
+  ...       <time type="date">2009/08/28 19:12:34.428 GMT+9</time>
+  ...     </workflow_action>
+  ...     <workflow_action id="edit_workflow">
+  ...       <time type="date">2009/08/28 19:12:34.426 GMT+9</time>
+  ...     </workflow_action>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> erp5diff.compare(old_xml, new_xml)
+  >>> erp5diff.output()
+  <xupdate:modifications xmlns:xupdate="http://www.xmldb.org/xupdate" version="1.0">
+    <xupdate:remove select="/object[@id='313730']/workflow_action[2]"/>
+    <xupdate:remove select="/object[@id='313730']/workflow_action[3]"/>
+    <xupdate:remove select="/object[@id='313730']/workflow_action[4]"/>
+  </xupdate:modifications>
+
+21. Modify two elements that have same id
+
+ As well as No.20. This a known bug, too.
+
+  >>> old_xml = """                                                                                                       
+  ... <erp5>                                                                                                              
+  ...   <object portal_type="Person" id="313730">
+  ...     <workflow_action id="edit_workflow">
+  ...       <time type="date">2009/08/28 19:12:34.424 GMT+9</time>
+  ...     </workflow_action>
+  ...     <workflow_action id="edit_workflow">
+  ...       <time type="date">2009/08/28 19:12:34.432 GMT+9</time>
+  ...     </workflow_action>
+  ...     <workflow_action id="edit_workflow">
+  ...       <time type="date">2009/08/28 19:12:34.434 GMT+9</time>
+  ...     </workflow_action>
+  ...     <workflow_action id="edit_workflow">
+  ...       <time type="date">2009/08/28 19:12:34.436 GMT+9</time>
+  ...     </workflow_action>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> new_xml = """
+  ... <erp5>
+  ...   <object portal_type="Person" id="313730">
+  ...     <workflow_action id="edit_workflow">
+  ...       <time type="date">2009/08/28 19:12:34.424 GMT+9</time>
+  ...     </workflow_action>
+  ...     <workflow_action id="edit_workflow">
+  ...       <time type="date">2009/08/29 19:12:34.432 GMT+9</time>
+  ...     </workflow_action>
+  ...     <workflow_action id="edit_workflow">
+  ...       <time type="date">2009/08/30 19:12:34.434 GMT+9</time>
+  ...     </workflow_action>
+  ...     <workflow_action id="edit_workflow">
+  ...       <time type="date">2009/08/31 19:12:34.436 GMT+9</time>
+  ...     </workflow_action>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> erp5diff.compare(old_xml, new_xml)
+  >>> erp5diff.output()
+  <xupdate:modifications xmlns:xupdate="http://www.xmldb.org/xupdate" version="1.0">
+    <xupdate:update select="/object[@id='313730']/workflow_action[2]/time">2009/08/29 19:12:34.432 GMT+9</xupdate:update>
+    <xupdate:update select="/object[@id='313730']/workflow_action[3]/time">2009/08/30 19:12:34.434 GMT+9</xupdate:update>     
+    <xupdate:update select="/object[@id='313730']/workflow_action[4]/time">2009/08/31 19:12:34.436 GMT+9</xupdate:update>   
+  </xupdate:modifications>
+
+22. Modify attributes of sequencial objects
+
+ ERP5Diff creates target index from 0 as a XPath string, but according to the 
+ definition of the XPath specification <http://www.w3.org/TR/xpath>, it is wrong. 
+ It should be start from 1. This is a known problem.
+
+  >>> old_xml = """
+  ... <erp5>
+  ...   <object portal_type="Test">
+  ...     <title>A</title>
+  ...   </object>
+  ...   <object portal_type="Test">
+  ...     <title>A</title>
+  ...   </object>
+  ...   <object portal_type="Test">
+  ...     <title>A</title>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> new_xml = """
+  ... <erp5>
+  ...   <object portal_type="Test">
+  ...     <title>A</title>
+  ...   </object>
+  ...   <object portal_type="Test">
+  ...     <title>B</title>
+  ...   </object>
+  ...   <object portal_type="Test">
+  ...     <title>C</title>
+  ...   </object>
+  ... </erp5>
+  ... """
+  >>> erp5diff.compare(old_xml, new_xml)
+  >>> erp5diff.output()
+  <xupdate:modifications xmlns:xupdate="http://www.xmldb.org/xupdate" version="1.0">
+    <xupdate:update select="/object[2]/title">B</xupdate:update>
+    <xupdate:update select="/object[3]/title">C</xupdate:update>
+  </xupdate:modifications>
+
+
 - 2003-12-04, Yoshinori OKUJI <yo at nexedi.com>
+- 2009-09-15, Tatuya Kamada <tatuya at nexedi.com>

Added: erp5/trunk/utils/erp5diff/bootstrap.py
URL: http://svn.erp5.org/erp5/trunk/utils/erp5diff/bootstrap.py?rev=29964&view=auto
==============================================================================
--- erp5/trunk/utils/erp5diff/bootstrap.py (added)
+++ erp5/trunk/utils/erp5diff/bootstrap.py [utf8] Fri Oct 23 11:29:21 2009
@@ -1,0 +1,56 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Bootstrap a buildout-based project
+
+Simply run this script in a directory containing a buildout.cfg.
+The script accepts buildout command-line options, so you can
+use the -c option to specify an alternate configuration file.
+
+$Id: bootstrap.py 77225 2007-06-29 09:20:13Z dobe $
+"""
+
+import os, shutil, sys, tempfile, urllib2
+
+tmpeggs = tempfile.mkdtemp()
+
+try:
+    import pkg_resources
+except ImportError:
+    ez = {}
+    exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
+                         ).read() in ez
+    ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
+
+    import pkg_resources
+
+cmd = 'from setuptools.command.easy_install import main; main()'
+if sys.platform == 'win32':
+    cmd = '"%s"' % cmd # work around spawn lamosity on windows
+
+ws = pkg_resources.working_set
+assert os.spawnle(
+    os.P_WAIT, sys.executable, sys.executable,
+    '-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout',
+    dict(os.environ,
+         PYTHONPATH=
+         ws.find(pkg_resources.Requirement.parse('setuptools')).location
+         ),
+    ) == 0
+
+ws.add_entry(tmpeggs)
+ws.require('zc.buildout')
+import zc.buildout.buildout
+zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap'])
+shutil.rmtree(tmpeggs)
+

Added: erp5/trunk/utils/erp5diff/buildout.cfg
URL: http://svn.erp5.org/erp5/trunk/utils/erp5diff/buildout.cfg?rev=29964&view=auto
==============================================================================
--- erp5/trunk/utils/erp5diff/buildout.cfg (added)
+++ erp5/trunk/utils/erp5diff/buildout.cfg [utf8] Fri Oct 23 11:29:21 2009
@@ -1,0 +1,8 @@
+[buildout]
+develop = .
+parts = test
+
+[test]
+recipe = zc.recipe.testrunner
+eggs = erp5diff
+

Added: erp5/trunk/utils/erp5diff/interfaces/__init__.py
URL: http://svn.erp5.org/erp5/trunk/utils/erp5diff/interfaces/__init__.py?rev=29964&view=auto
==============================================================================
    (empty)

Added: erp5/trunk/utils/erp5diff/interfaces/erp5diff.py
URL: http://svn.erp5.org/erp5/trunk/utils/erp5diff/interfaces/erp5diff.py?rev=29964&view=auto
==============================================================================
--- erp5/trunk/utils/erp5diff/interfaces/erp5diff.py (added)
+++ erp5/trunk/utils/erp5diff/interfaces/erp5diff.py [utf8] Fri Oct 23 11:29:21 2009
@@ -1,0 +1,41 @@
+from zope.interface import Interface
+
+class IERP5Diff(Interface):
+  """
+    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 attrib '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.
+  """
+
+  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.
+    """
+
+  def output(self, output_file=None):
+    """
+      Output the result of parsing XML documents to 'output_file'.
+      If it is not specified, stdout is assumed.
+    """
+
+
+  def outputString(self):
+    """
+      Return the result as a string object.
+    """
+
+  def main():
+    """
+      The main routine of ERP5Diff.
+    """

Added: erp5/trunk/utils/erp5diff/setup.cfg
URL: http://svn.erp5.org/erp5/trunk/utils/erp5diff/setup.cfg?rev=29964&view=auto
==============================================================================
--- erp5/trunk/utils/erp5diff/setup.cfg (added)
+++ erp5/trunk/utils/erp5diff/setup.cfg [utf8] Fri Oct 23 11:29:21 2009
@@ -1,0 +1,5 @@
+[egg_info]
+tag_build = 
+tag_date = 0
+tag_svn_revision = 0
+

Modified: erp5/trunk/utils/erp5diff/setup.py
URL: http://svn.erp5.org/erp5/trunk/utils/erp5diff/setup.py?rev=29964&r1=29963&r2=29964&view=diff
==============================================================================
--- erp5/trunk/utils/erp5diff/setup.py [utf8] (original)
+++ erp5/trunk/utils/erp5diff/setup.py [utf8] Fri Oct 23 11:29:21 2009
@@ -1,6 +1,6 @@
 #! /usr/bin/env python
 
-from distutils.core import setup
+from setuptools import setup, find_packages
 
 setup(name="erp5diff",
       version="0.1",
@@ -9,7 +9,11 @@
       author_email="yo at nexedi.com",
       url="http://nexedi.com",
       license="GPL",
+      packages=find_packages(),
       py_modules=["ERP5Diff"],
       scripts=["erp5diff"],
-      data_files=[('share/man/man1', ['erp5diff.1'])]
+      data_files=[('share/man/man1', ['erp5diff.1'])],
+      install_requires=[ 'zope.interface', 'lxml'],
+      include_package_data=True,
+      zip_safe=False,
      )

Added: erp5/trunk/utils/erp5diff/tests.py
URL: http://svn.erp5.org/erp5/trunk/utils/erp5diff/tests.py?rev=29964&view=auto
==============================================================================
--- erp5/trunk/utils/erp5diff/tests.py (added)
+++ erp5/trunk/utils/erp5diff/tests.py [utf8] Fri Oct 23 11:29:21 2009
@@ -1,0 +1,23 @@
+from zope import interface
+
+import zope.testing
+import unittest
+
+OPTIONFLAGS = (zope.testing.doctest.ELLIPSIS |
+               zope.testing.doctest.NORMALIZE_WHITESPACE)
+
+
+def test_suite():
+    doctests = ('README',)
+
+    globs = dict(interface=interface)
+  
+    return unittest.TestSuite((
+        zope.testing.doctest.DocFileSuite(doctest,
+                                          optionflags=OPTIONFLAGS,
+                                          globs=globs,
+                                          ) for doctest in doctests
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')




More information about the Erp5-report mailing list