ERP5Conduit.py 27.2 KB
Newer Older
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
##############################################################################
#
# Copyright (c) 2002 Nexedi SARL and Contributors. All Rights Reserved.
#          Sebastien Robin <seb@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# 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 Products.ERP5SyncML.SyncCode import SyncCode
30
from Products.ERP5SyncML.XMLSyncUtils import XMLSyncUtilsMixin
Jean-Paul Smets's avatar
Jean-Paul Smets committed
31 32 33 34 35 36
from Products.ERP5SyncML.Subscription import Conflict
from Products.CMFCore.utils import getToolByName
from Products.ERP5SyncML.XupdateUtils import XupdateUtils
from Products.ERP5Type.Utils import convertToUpperCase
from Products.ERP5Type.Accessor.TypeDefinition import list_types
from xml.dom.ext.reader.Sax2 import FromXml
37
from DateTime.DateTime import DateTime
Jean-Paul Smets's avatar
Jean-Paul Smets committed
38 39 40
from email.MIMEBase import MIMEBase
from email import Encoders

41
import re, copy
Jean-Paul Smets's avatar
Jean-Paul Smets committed
42 43 44

from zLOG import LOG

45
class ERP5Conduit(XMLSyncUtilsMixin):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
  """
    A conduit is a piece of code in charge of

    - updating an object attributes from an XUpdate XML stream

    (Conduits are not in charge of creating new objects which
    are eventually missing in a synchronisation process)

    If an object has be created during a synchronisation process,
    the way to proceed consists in:

    1- creating an empty instance of the appropriate class
      in the appropriate directory

    2- updating that empty instance with the conduit

    The first implementation of ERP5 synchronisation
    will define a default location to create new objects and
    a default class. This will be defined at the level of the synchronisation
    tool

67 68 69 70 71 72 73 74 75 76 77 78 79 80
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXx
    Look carefully when we are adding elements,
    for example, when we do 'insert-after', with 2 xupdate:element,
    so adding 2 differents objects, actually it adds only XXXX one XXX object
    In this case the getSubObjectDepth(), doesn't have
    too much sence
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    There is also one problem, when we synchronize a conflict, we are not waiting
    the response of the client, so that we are not sure if it take into account,
    we may have CONFLICT_NOT_SYNCHRONIZED AND CONFLICT_SYNCHRONIZED
    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Jean-Paul Smets's avatar
Jean-Paul Smets committed
81

82
  """
Jean-Paul Smets's avatar
Jean-Paul Smets committed
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102


  def getEncoding(self):
    """
    return the string corresponding to the local encoding
    """
    return "iso-8859-1"

  def __init__(self):
    self.args = {}

  def addNode(self, xml=None, object=None, previous_xml=None, force=0, **kw):
    """
    A node is added

    This fucntion returns conflict_list, wich is of the form,
    [conflict1,conflict2,...] where conclict1 is of the form :
    [object.getPath(),keyword,local_and_actual_value,remote_value]
    """
    conflict_list = []
103
    xml = self.convertToXml(xml)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
104 105 106 107
    LOG('addNode',0,'xml_reconstitued: %s' % str(xml))
    # In the case where this new node is a object to add
    LOG('addNode',0,'object.id: %s' % object.getId())
    LOG('addNode',0,'xml.nodeName: %s' % xml.nodeName)
108
    LOG('addNode',0,'isSubObjectAdd: %i' % self.getSubObjectDepth(xml))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
109
    if xml.nodeName == 'object' \
110
       or xml.nodeName in self.XUPDATE_INSERT_OR_ADD and self.getSubObjectDepth(xml)==1:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
111
      object_id = self.getObjectId(xml)
112
      docid = self.getObjectDocid(xml)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
113 114 115
      LOG('addNode',0,'object_id: %s' % object_id)
      if object_id is not None:
        try:
116 117 118 119 120 121 122 123
          subobject = object._getOb(object_id)
        except (AttributeError, KeyError):
          subobject = None
        #subobject = None
        #try:
        #  subobject = object[object_id]
        #except KeyError:
        #  pass
Jean-Paul Smets's avatar
Jean-Paul Smets committed
124 125 126 127
        if subobject is None: # If so it does'nt exist yes
          portal_type = ''
          if xml.nodeName == 'object':
            portal_type = self.getObjectType(xml)
128
          elif xml.nodeName in self.XUPDATE_INSERT_OR_ADD:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
129 130 131
            portal_type = self.getXupdateObjectType(xml)
          portal_types = getToolByName(object,'portal_types')
          LOG('ERP5Conduit.addNode',0,'portal_type: |%s|' % str(portal_type))
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
          if docid==None: # ERP5 content
            portal_types.constructContent(type_name = portal_type,
                                              container = object,
                                              id = object_id)
          else: # CPS content
            # This is specific to CPS, we will call the proxy tool
            px_tool= getToolByName(object,'portal_proxies')
            proxy_type = 'document'
            if portal_type == 'Workspace':
              proxy_type = 'folder'
            proxy = px_tool.createEmptyProxy(proxy_type,
                                   object,portal_type,object_id,docid)
            #px_tool.createRevision(proxy,px_tool.getDefaultLanguage()) # Doesn't works well
            # px_tool._addProxy(proxy,None) # Doesn't works well
          #object.newContent(portal_type=portal_type, id=object_id) # Doesn't works with CPS
          #subobject = object[object_id] # Doesn't works with CPS
          subobject = object._getOb(object_id)
          # Again for CPS proxy XXX May be not needed
          #if docid is not None:
          #  subobject.proxyChanged()
Jean-Paul Smets's avatar
Jean-Paul Smets committed
152
        self.newObject(object=subobject,xml=xml)
153 154
    elif xml.nodeName in self.XUPDATE_INSERT_OR_ADD \
         and self.getSubObjectDepth(xml)==2:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
155 156
      # We should find the object corresponding to
      # this update, so we have to look in the previous_xml
157 158
      sub_object_id = self.getSubObjectId(xml)
      LOG('addNode',0,'getSubObjectModification number: %s' % sub_object_id)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
159
      #LOG('updateNode',0,'isSubObjectModification previous: %s' % str(previous_xml))
160 161 162
      if previous_xml is not None and sub_object_id is not None:
        LOG('addNode',0,'previous xml is not none and also sub_object_id')
        previous_xml = self.getSubObjectXml(sub_object_id,previous_xml)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
163 164 165 166
        # Get the id of the previous object
        i = 0
        sub_previous_xml = None
        # Find the previous xml corresponding to this subobject
167
        sub_previous_xml == self.getSubObjectXml(sub_object_id,previous_xml)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
168 169 170 171
        LOG('addNode',0,'isSubObjectModification sub_p_xml: %s' % str(sub_previous_xml))
        if sub_previous_xml is not None:
          sub_object = None
          try:
172
            sub_object = object[sub_object_id]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
          except KeyError:
            pass
          if sub_object is not None:
            LOG('addNode',0,'subobject.id: %s' % sub_object.id)
            # Change the xml in order to directly apply
            # modifications to the subobject
            sub_xml = self.getSubObjectXupdate(xml)
            LOG('addNode',0,'sub_xml: %s' % str(sub_xml))
            # Then do the udpate
            conflict_list += self.addNode(xml=sub_xml,object=sub_object,
                            previous_xml=sub_previous_xml, force=force)
    else:
      conflict_list += self.updateNode(xml=xml,object=object, force=force, **kw)
    return conflict_list

188
  def deleteNode(self, xml=None, object=None, object_id=None, force=None, **kw):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
189 190 191 192 193
    """
    A node is deleted
    """
    # In the case where this new node is a object to delete
    LOG('ERP5Conduit',0,'deleteNode')
194 195 196 197 198 199 200 201 202 203 204 205
    LOG('ERP5Conduit',0,'deleteNode, object.id: %s' % object.getId())
    conflict_list = []
    xml = self.convertToXml(xml)
    if object_id is None:
      LOG('ERP5Conduit',0,'deleteNode, SubObjectDepth: %i' % self.getSubObjectDepth(xml))
      if xml.nodeName == self.xml_object_tag:
        object_id = self.getObjectId(xml)
      elif self.getSubObjectDepth(xml)==1:
        object_id = self.getSubObjectId(xml)
      elif self.getSubObjectDepth(xml)==2:
        # we have to call delete node on a subsubobject
        sub_object_id = self.getSubObjectId(xml)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
206
        try:
207 208 209 210
          sub_object = object._getOb(sub_object_id)
          sub_xml = self.getSubObjectXupdate(xml)
          conflict_list += self.deleteNode(xml=sub_xml,object=sub_object,
                                           force=force)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
211 212
        except KeyError:
          pass
213 214 215 216 217 218
    else: # We do have an object_id
      try:
        object._delObject(object_id)
      except (AttributeError, KeyError):
        pass
    return conflict_list
Jean-Paul Smets's avatar
Jean-Paul Smets committed
219 220 221 222 223 224 225 226 227 228

  def updateNode(self, xml=None, object=None, previous_xml=None, force=0, **kw):
    """
    A node is updated with some xupdate
      - xml : the xml corresponding to the update, it should be xupdate
      - object : the object on wich we want to apply the xupdate
      - [previous_xml] : the previous xml of the object, it is mandatory
                         when we have sub objects
    """
    conflict_list = []
229
    xml = self.convertToXml(xml)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
230 231 232 233 234 235 236 237 238
    LOG('updateNode',0,'xml.nodeName: %s' % xml.nodeName)
    # we have an xupdate xml
    if xml.nodeName == 'xupdate:modifications':
      xupdate_utils = XupdateUtils()
      conflict_list += xupdate_utils.applyXupdate(object=object,xupdate=xml,conduit=self,
                                 previous_xml=previous_xml, force=force)
    # we may have only the part of an xupdate
    else:
      args = {}
239
      LOG('isSubObjectModification',0,'result: %s' % str(self.isSubObjectModification(xml)))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
240 241 242
      if self.isProperty(xml) and not(self.isSubObjectModification(xml)):
        for subnode in xml.attributes:
          if subnode.nodeType == subnode.ATTRIBUTE_NODE and subnode.nodeName=='select':
243 244 245
            LOG('updateNode',0,'selection: %s' % str(subnode.nodeValue))
            select_list = subnode.nodeValue.split('/') # Something like:
                                                       #('','object[1]','sid[1]')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
246 247
            new_select_list = ()
            for select_item in select_list:
248 249 250
              if select_item.find('[')>=0:
                select_item = select_item[:select_item.find('[')]
              new_select_list += (select_item,)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
251 252 253
            select_list = new_select_list # Something like : ('','object','sid')
            keyword = select_list[len(select_list)-1] # this will be 'sid'

254 255
        LOG('updateNode',0,'keyword: %s' % str(keyword))
        if not (xml.nodeName in self.XUPDATE_INSERT_OR_ADD):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
256 257 258 259 260 261 262 263
          for subnode in self.getElementNodeList(xml):
            if subnode.nodeName=='xupdate:element':
              for subnode1 in subnode.attributes:
                if subnode1.nodeName=='name':
                  keyword = subnode1.nodeValue
        if not (keyword in self.NOT_EDITABLE_PROPERTY):
          # We will look for the data to enter
          if len(self.getElementNodeList(xml))==0:
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
            try:
              data = xml.childNodes[0].data
            except IndexError: # There is no data
              data = None
          # XXX may be not needed any more
          #else:
          #  data=()
          #  for subnode in self.getElementNodeList(xml):
          #    element_data = subnode.childNodes[0].data
          #    element_data = self.convertXmlValue(element_data)
          #    data += (element_data,)
          #  if len(data) == 1: # This is probably because this is not a list
                               # but a string XXX may be not good
          #    data = data[0]
          data_type = object.getPropertyType(keyword)
          LOG('updateNode',0,'data_type: %s' % str(data_type))
          data = self.convertXmlValue(data,data_type=data_type)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
281 282 283 284 285 286 287 288 289
          args[keyword] = data
          args = self.getFormatedArgs(args=args)
          # This is the place where we should look for conflicts
          # For that we need :
          #   - data : the data from the remote box
          #   - old_data : the data from this box but at the time of the last synchronization
          #   - current_data : the data actually on this box
          isConflict = 0
          if previous_xml is not None: # if no previous_xml, no conflict
290
            old_data = self.getObjectProperty(keyword,previous_xml,data_type=data_type)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
291 292 293 294 295
            current_data = object.getProperty(keyword)
            LOG('updateNode',0,'Conflict data: %s' % str(data))
            LOG('updateNode',0,'Conflict old_data: %s' % str(old_data))
            LOG('updateNode',0,'Conflict current_data: %s' % str(current_data))
            if (old_data != current_data) and (data != current_data):
296 297 298 299 300 301 302 303 304 305
              LOG('updateNode',0,'Conflict on : %s' % keyword)
              # Hack in order to get the synchronization working for demo
              # XXX this have to be removed after
              if not (data_type in self.binary_type_list):
                # This is a conflict
                isConflict = 1
                conflict_list += [Conflict(object_path=object.getPhysicalPath(),
                                           keyword=keyword,
                                           local_value=current_data,
                                           remote_value=data)]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
306 307
          # We will now apply the argument with the method edit
          if args != {} and (isConflict==0 or force):
308 309
            LOG('updateNode',0,'object._edit, args: %s' % str(args))
            object._edit(**args)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
310 311 312 313 314 315 316
        if keyword == 'object':
          # This is the case where we have to call addNode
          LOG('updateNode',0,'we will add sub-object')
          conflict_list += self.addNode(xml=subnode,object=object,force=force)
      elif self.isSubObjectModification(xml):
        # We should find the object corresponding to
        # this update, so we have to look in the previous_xml
317 318 319 320 321
        sub_object_id = self.getSubObjectId(xml)
        LOG('updateNode',0,'getSubObjectModification number: %s' % sub_object_id)
        if previous_xml is not None and sub_object_id is not None:
          LOG('updateNode',0,'previous xml is not none and also sub_object_id')
          sub_previous_xml = self.getSubObjectXml(sub_object_id,previous_xml)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
322 323 324 325
          LOG('updateNode',0,'isSubObjectModification sub_p_xml: %s' % str(sub_previous_xml))
          if sub_previous_xml is not None:
            sub_object = None
            try:
326
              sub_object = object[sub_object_id]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
            except KeyError:
              pass
            if sub_object is not None:
              LOG('updateNode',0,'subobject.id: %s' % sub_object.id)
              # Change the xml in order to directly apply
              # modifications to the subobject
              sub_xml = self.getSubObjectXupdate(xml)
              LOG('updateNode',0,'sub_xml: %s' % str(sub_xml))
              # Then do the udpate
              conflict_list += self.updateNode(xml=sub_xml,object=sub_object, force=force,
                              previous_xml=sub_previous_xml)
    return conflict_list

  def getFormatedArgs(self, args=None):
    """
    This lookd inside the args dictionnary and then
    convert any unicode string to string
    """
    LOG('ERP5Conduit.getFormatedArgs',0,'args: %s' % str(args))
    new_args = {}
    for keyword in args.keys():
      data = args[keyword]
      if type(keyword) is type(u"a"):
        keyword = keyword.encode(self.getEncoding())
      if type(data) is type([]) or type(data) is type(()):
        new_data = []
        for item in data:
          if type(item) is type(u"a"):
            item = item.encode(self.getEncoding())
356
            item = item.replace('@@@','\n')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
357 358 359 360
          new_data += [item]
        data = new_data
      if type(data) is type(u"a"):
        data = data.encode(self.getEncoding())
361
        data = data.replace('@@@','\n')
Jean-Paul Smets's avatar
Jean-Paul Smets committed
362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388
      if keyword == 'binary_data':
        LOG('ERP5Conduit.getFormatedArgs',0,'binary_data keyword: %s' % str(keyword))
        msg = MIMEBase('application','octet-stream')
        Encoders.encode_base64(msg)
        msg.set_payload(data)
        data = msg.get_payload(decode=1)
      new_args[keyword] = data
    return new_args

  def isProperty(self, xml):
    """
    Check if it is a simple property
    """
    bad_list = ('/object[1]/object','/object[1]/workflow_history','/object[1]/security_info')
    for subnode in xml.attributes:
      if subnode.nodeType == subnode.ATTRIBUTE_NODE and subnode.nodeName=='select':
        value = subnode.nodeValue
        for bad_string in bad_list:
          if value.find(bad_string)==0:
            return 0
    return 1

  def getSubObjectXupdate(self, xml):
    """
    This will change the xml in order to change the update
    from the object to the subobject
    """
389 390 391 392
    xml = copy.deepcopy(xml)
    for subnode in self.getAttributeNodeList(xml):
      if subnode.nodeName=='select':
        subnode.nodeValue = self.getSubObjectSelect(subnode.nodeValue)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
393 394 395 396 397 398 399 400
    element_node_list = self.getElementNodeList(xml)
    # This is when we have a sub_sub_object_add and we will want a sub_object_add
    LOG('getSubObjectXupdate',0,'xml.nodeName: %s' % xml.nodeName)
    for subnode in self.getElementNodeList(xml):
      if subnode.nodeName=='xupdate:element':
        LOG('getSubObjectXupdate',0,'subnode.nodeName: %s' % subnode.nodeName)
        for subnode1 in self.getElementNodeList(subnode):
          LOG('getSubObjectXupdate',0,'subnode1.nodeName: %s' % subnode1.nodeName)
401
          if subnode1.nodeName=='object':
Jean-Paul Smets's avatar
Jean-Paul Smets committed
402 403 404 405 406 407 408
            return subnode1
    return xml

  def isSubObjectModification(self, xml):
    """
    Check if it is a modification from an subobject
    """
409
    good_list = (self.sub_object_exp,)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
410 411 412 413 414
    for subnode in xml.attributes:
      if subnode.nodeType == subnode.ATTRIBUTE_NODE and subnode.nodeName=='select':
        value = subnode.nodeValue
        LOG('isSubObjectModification',0,'value: %s' % value)
        for good_string in good_list:
415
          if re.search(good_string,value) is not None:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
416 417 418
            return 1
    return 0

419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440
  def getSubObjectDepth(self, xml):
    """
    Give the Depth of a subobject modification
    0 means, no depth
    1 means it is a subobject
    2 means it is more depth than subobject
    """
    LOG('getSubObjectDepth',0,'xml.nodeName: %s' % xml.nodeName)
    if xml.nodeName in self.XUPDATE_TAG:
      LOG('getSubObjectDepth',0,'xml2.nodeName: %s' % xml.nodeName)
      if xml.nodeName == self.xml_object_tag:
        return 1
      for subnode in self.getAttributeNodeList(xml):
        LOG('getSubObjectDepth',0,'subnode.nodeName: %s' % subnode.nodeName)
        if subnode.nodeName == 'select':
          value = subnode.nodeValue
          LOG('getSubObjectDepth',0,'subnode.nodeValue: %s' % subnode.nodeValue)
          if re.search(self.sub_object_exp,value) is not None:
            new_select = self.getSubObjectSelect(value)
            if self.getSubObjectSelect(new_select) != new_select:
              return 2
            return 1
Jean-Paul Smets's avatar
Jean-Paul Smets committed
441
      for subnode in self.getElementNodeList(xml):
442
        # One part of this is specific to xmldiff, may be need to rewrite
Jean-Paul Smets's avatar
Jean-Paul Smets committed
443 444 445 446
        if subnode.nodeName == 'xupdate:element':
          for attribute in subnode.attributes:
            if attribute.nodeName == 'name':
              is_sub_add = 0
447
              if attribute.nodeValue == self.xml_object_tag:
Jean-Paul Smets's avatar
Jean-Paul Smets committed
448 449 450 451
                is_sub_add = 1
              if not is_sub_add:
                return 0
              for subnode1 in self.getElementNodeList(subnode):
452
                if subnode1.nodeName == self.xml_object_tag: # In this particular case, this is sub_sub_add
Jean-Paul Smets's avatar
Jean-Paul Smets committed
453 454 455 456
                  return 2
              return 1
    return 0

457
  def getSubObjectSelect(self, select):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
458
    """
459 460 461
    Return a string wich is the selection for the subobject
    ex: for "/object[@id='161']/object[@id='default_address']/street_address"
    if returns "/object[@id='default_address']/street_address"
Jean-Paul Smets's avatar
Jean-Paul Smets committed
462
    """
463 464 465 466 467 468 469 470 471 472 473 474 475
    if re.search(self.sub_object_exp,select) is not None:
      s = self.xml_object_tag
      new_value = '/' + select[select.find(s,select.find(s)+1):]
      select = new_value
    return select

  def getSubObjectId(self, xml):
    """
    Return the id of the subobject in an xupdate modification
    """
    object_id = None
    for subnode in self.getAttributeNodeList(xml):
      if subnode.nodeName=='select':
Jean-Paul Smets's avatar
Jean-Paul Smets committed
476
        value = subnode.nodeValue
477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495
        if re.search(self.sub_object_exp,value) is not None:
          s = self.xml_object_tag
          object_id = value[value.find(s,value.find(s)+1):]
          object_id = object_id[object_id.find("'")+1:]
          object_id = object_id[:object_id.find("'")]
          return object_id
    return object_id

  def getSubObjectXml(self, object_id, xml):
    """
    Return the xml of the subobject which as the id object_id
    """
    xml = self.convertToXml(xml)
    for subnode in self.getElementNodeList(xml):
      if subnode.nodeName==self.xml_object_tag:
        LOG('getSub0bjectXml: object_id:',0,object_id)
        if object_id == self.getObjectId(subnode):
          return subnode
    return None
Jean-Paul Smets's avatar
Jean-Paul Smets committed
496 497 498 499 500

  def getObjectId(self, xml):
    """
    Retrieve the id
    """
501 502 503 504 505 506 507
    #for subnode in self.getElementNodeList(xml):
    #  if subnode.nodeName == 'id':
    #    data = subnode.childNodes[0].data
    #    return self.convertXmlValue(data)
    for attribute in self.getAttributeNodeList(xml):
      if attribute.nodeName == 'id':
        data = attribute.childNodes[0].data
Jean-Paul Smets's avatar
Jean-Paul Smets committed
508 509 510 511 512 513 514 515 516 517 518
        return self.convertXmlValue(data)
    # In the case where the new object is inside an xupdate
    if xml.nodeName.find('xupdate')>=0:
      for subnode in self.getElementNodeList(xml):
        if subnode.nodeName == 'xupdate:element':
          for subnode1 in self.getElementNodeList(subnode):
            if subnode1.nodeName == 'id':
              data = subnode1.childNodes[0].data
              return self.convertXmlValue(data)
    return None

519 520 521 522 523 524 525 526 527
  def getObjectDocid(self, xml):
    """
    Retrieve the docid
    """
    for subnode in self.getElementNodeList(xml):
      if subnode.nodeName == 'docid':
        data = subnode.childNodes[0].data
        return self.convertXmlValue(data)
    return None
Jean-Paul Smets's avatar
Jean-Paul Smets committed
528

529
  def getObjectProperty(self, property, xml, data_type=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
530 531 532
    """
    Retrieve the given property
    """
533
    xml = self.convertToXml(xml)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
534 535 536
    # document, with childNodes[0] a DocumentType and childNodes[1] the Element Node
    for subnode in self.getElementNodeList(xml):
      if subnode.nodeName == property:
537 538 539 540 541
        try:
          data = subnode.childNodes[0].data
          data =  self.convertXmlValue(data, data_type=data_type)
        except IndexError: # There is no data
          data = None
Jean-Paul Smets's avatar
Jean-Paul Smets committed
542 543 544
        return data
    return None

545
  def convertToXml(self,xml):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
546 547 548 549 550 551
    """
    if xml is a string, convert it to a node
    """
    if type(xml) in (type('a'),type(u'a')):
      xml = FromXml(xml)
      xml = xml.childNodes[1] # Because we just created a new xml
552 553 554
    # If we have the xml from the node erp5, we just take the subnode
    if xml.nodeName=='erp5':
      xml = self.getElementNodeList(xml)[0]
Jean-Paul Smets's avatar
Jean-Paul Smets committed
555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611
    return xml

  def getObjectType(self, xml):
    """
    Retrieve the portal type from an xml
    """
    portal_type = None
    for subnode in xml.attributes:
      if subnode.nodeType == subnode.ATTRIBUTE_NODE:
        if subnode.nodeName=='portal_type':
          portal_type = subnode.nodeValue
          portal_type = self.convertXmlValue(portal_type)
          return portal_type
    return portal_type

  def getXupdateObjectType(self, xml):
    """
    Retrieve the portal type from an xupdate
    """
    if xml.nodeName.find('xupdate')>=0:
      for subnode in self.getElementNodeList(xml):
        if subnode.nodeName == 'xupdate:element':
          for subnode1 in self.getElementNodeList(subnode):
            if subnode1.nodeName == 'xupdate:attribute':
              for attribute in subnode1.attributes:
                if attribute.nodeName == 'name':
                  if attribute.nodeValue == 'portal_type':
                    data = subnode1.childNodes[0].data
                    data = self.convertXmlValue(data)
                    return data
    return None


  def newObject(self, object=None, xml=None):
    """
      modify the object with datas from
      the xml (action section)
    """
    args = {}
    if xml.nodeName.find('xupdate')>= 0:
      xml = self.getElementNodeList(xml)[0]
    for subnode in self.getElementNodeList(xml):
      if not(subnode.nodeName in self.NOT_EDITABLE_PROPERTY):
        keyword_type = None
        for subnode1 in subnode.attributes:
          if subnode1.nodeType == subnode1.ATTRIBUTE_NODE:
            if subnode1.nodeName=='type':
              keyword_type = subnode1.nodeValue

        LOG('newObject',0,str(subnode.childNodes))
        # This is the case where the property is a list
        keyword=str(subnode.nodeName)
        if len(subnode.childNodes) > 0: # We check that this tag is not empty
          data = subnode.childNodes[0].data
          args[keyword]=data
        LOG('newObject',0,'keyword: %s' % str(keyword))
        LOG('newObject',0,'keywordtype: %s' % str(keyword_type))
612 613
        #if args.has_key(keyword):
        #  LOG('newObject',0,'data: %s' % str(args[keyword]))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
614
        if args.has_key(keyword):
615
          args[keyword] = self.convertXmlValue(args[keyword],keyword_type)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
616 617 618 619 620 621
    # We should first edit the object
    args = self.getFormatedArgs(args=args)
    LOG('newObject',0,"object.getpath: %s" % str(object.getPath()))
    LOG('newObject',0,"args: %s" % str(args))
    # edit the object with a dictionnary of arguments,
    # like {"telephone_number":"02-5648"}
622
    object._edit(**args)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
623 624 625 626 627 628 629

    # Then we may create subobject
    for subnode in xml.childNodes:
      if subnode.nodeType == subnode.ELEMENT_NODE and \
                              subnode.nodeName=='object':
        self.addNode(object=object,xml=subnode)

630
  def convertXmlValue(self, data, data_type=None):
Jean-Paul Smets's avatar
Jean-Paul Smets committed
631 632
    """
    It is possible that the xml change the value, for example
633 634 635 636 637 638 639
    there is some too much '\n' and some spaces. We have to do some extra
    things so that we convert correctly the vlalue
    """
    if data is None:
      if data_type in self.list_type_list:
        data = ()
      return data
Jean-Paul Smets's avatar
Jean-Paul Smets committed
640 641 642
    data = data.replace('\n','')
    if type(data) is type(u"a"):
      data = data.encode(self.getEncoding())
643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661
    # We can now convert string in tuple, dict, binary...
    if data_type in self.list_type_list:
      if type(data) is type('a'):
        data = tuple(data.split('@@@'))
    elif data_type in self.text_type_list:
      data = data.replace('@@@','\n')
    elif data_type in self.binary_type_list:
      data = data.replace('@@@','\n')
      msg = MIMEBase('application','octet-stream')
      Encoders.encode_base64(msg)
      msg.set_payload(data)
      data = msg.get_payload(decode=1)
    elif data_type in self.date_type_list:
      data = DateTime(data)
    elif data_type in self.dict_type_list:
      dict_list = map(lambda x:x.split(':'),data[1:-1].split(','))
      data = map(lambda (x,y):(x.replace(' ','').replace("'",''),int(y)),dict_list)
      data = dict(data)
    LOG('convertXmlValue',0,'data: %s' % str(data))
Jean-Paul Smets's avatar
Jean-Paul Smets committed
662 663
    return data