Back to index

plone3  3.1.7
utils.py
Go to the documentation of this file.
00001 ##############################################################################
00002 #
00003 # Copyright (c) 2004 Zope Corporation and Contributors. All Rights Reserved.
00004 #
00005 # This software is subject to the provisions of the Zope Public License,
00006 # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
00007 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
00008 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00009 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
00010 # FOR A PARTICULAR PURPOSE.
00011 #
00012 ##############################################################################
00013 """ GenericSetup product utilities
00014 
00015 $Id: utils.py 84270 2008-02-26 15:20:45Z mauritsvanrees $
00016 """
00017 
00018 import os
00019 import sys
00020 from cgi import escape
00021 from inspect import getdoc
00022 from xml.dom.minidom import _nssplit
00023 from xml.dom.minidom import Document
00024 from xml.dom.minidom import Element
00025 from xml.dom.minidom import Node
00026 from xml.dom.minidom import parseString
00027 from xml.sax.handler import ContentHandler
00028 from xml.parsers.expat import ExpatError
00029 
00030 import Products
00031 from AccessControl import ClassSecurityInfo
00032 from Acquisition import Implicit
00033 from Globals import InitializeClass
00034 from Globals import package_home
00035 from OFS.interfaces import IOrderedContainer
00036 from Products.Five.utilities.interfaces import IMarkerInterfaces
00037 from zope.component import queryMultiAdapter
00038 from zope.deprecation import deprecated
00039 from zope.interface import directlyProvides
00040 from zope.interface import implements
00041 from zope.interface import implementsOnly
00042 from zope.interface import providedBy
00043 from ZPublisher.HTTPRequest import default_encoding
00044 
00045 from exceptions import BadRequest
00046 from interfaces import IBody
00047 from interfaces import INode
00048 from interfaces import ISetupContext
00049 from interfaces import ISetupTool
00050 from permissions import ManagePortal
00051 
00052 
00053 _pkgdir = package_home( globals() )
00054 _wwwdir = os.path.join( _pkgdir, 'www' )
00055 _xmldir = os.path.join( _pkgdir, 'xml' )
00056 
00057 # Please note that these values may change. Always import 
00058 # the values from here instead of using the values directly.
00059 CONVERTER, DEFAULT, KEY = 1, 2, 3
00060 I18NURI = 'http://xml.zope.org/namespaces/i18n'
00061 
00062 
00063 def _getDottedName( named ):
00064 
00065     if isinstance( named, basestring ):
00066         return str( named )
00067 
00068     try:
00069         dotted = '%s.%s' % (named.__module__, named.__name__)
00070     except AttributeError:
00071         raise ValueError('Cannot compute dotted name: %s' % named)
00072 
00073     # remove leading underscore names if possible
00074 
00075     # Step 1: check if there is a short version
00076     short_dotted = '.'.join([ n for n in dotted.split('.')
00077                               if not n.startswith('_') ])
00078     if short_dotted == dotted:
00079         return dotted
00080 
00081     # Step 2: check if short version can be resolved
00082     try:
00083         short_resolved = _resolveDottedName(short_dotted)
00084     except (ValueError, ImportError):
00085         return dotted
00086 
00087     # Step 3: check if long version resolves to the same object
00088     try:
00089         resolved = _resolveDottedName(dotted)
00090     except (ValueError, ImportError):
00091         raise ValueError('Cannot compute dotted name: %s' % named)
00092     if short_resolved is not resolved:
00093         return dotted
00094 
00095     return short_dotted
00096 
00097 def _resolveDottedName( dotted ):
00098 
00099     __traceback_info__ = dotted
00100 
00101     parts = dotted.split( '.' )
00102 
00103     if not parts:
00104         raise ValueError, "incomplete dotted name: %s" % dotted
00105 
00106     parts_copy = parts[:]
00107 
00108     while parts_copy:
00109         try:
00110             module = __import__( '.'.join( parts_copy ) )
00111             break
00112 
00113         except ImportError:
00114             # Reraise if the import error was caused inside the imported file
00115             if sys.exc_info()[2].tb_next is not None: raise
00116 
00117             del parts_copy[ -1 ]
00118 
00119             if not parts_copy:
00120                 return None
00121 
00122     parts = parts[ 1: ] # Funky semantics of __import__'s return value
00123 
00124     obj = module
00125 
00126     for part in parts:
00127         try:
00128             obj = getattr( obj, part )
00129         except AttributeError:
00130             return None
00131 
00132     return obj
00133 
00134 def _extractDocstring( func, default_title, default_description ):
00135 
00136     try:
00137         doc = getdoc( func )
00138         lines = doc.split( '\n' )
00139 
00140     except AttributeError:
00141 
00142         title = default_title
00143         description = default_description
00144 
00145     else:
00146         title = lines[ 0 ]
00147 
00148         if len( lines ) > 1 and lines[ 1 ].strip() == '':
00149             del lines[ 1 ]
00150 
00151         description = '\n'.join( lines[ 1: ] )
00152 
00153     return title, description
00154 
00155 
00156 deprecated('HandlerBase',
00157            'SAX based XML parsing is no longer supported by GenericSetup. '
00158            'HandlerBase will be removed in GenericSetup 1.5.')
00159 
00160 class HandlerBase( ContentHandler ):
00161 
00162     _encoding = None
00163     _MARKER = object()
00164 
00165     def _extract( self, attrs, key, default=None ):
00166 
00167         result = attrs.get( key, self._MARKER )
00168 
00169         if result is self._MARKER:
00170             return default
00171 
00172         return self._encode( result )
00173 
00174     def _extractBoolean( self, attrs, key, default ):
00175 
00176         result = attrs.get( key, self._MARKER )
00177 
00178         if result is self._MARKER:
00179             return default
00180 
00181         result = result.lower()
00182         return result in ( '1', 'yes', 'true' )
00183 
00184     def _encode( self, content ):
00185 
00186         if self._encoding is None:
00187             return content
00188 
00189         return content.encode( self._encoding )
00190 
00191 
00192 ##############################################################################
00193 # WARNING: PLEASE DON'T USE THE CONFIGURATOR PATTERN. THE RELATED BASE CLASSES
00194 # WILL BECOME DEPRECATED AS SOON AS GENERICSETUP ITSELF NO LONGER USES THEM.
00195 
00196 class ImportConfiguratorBase(Implicit):
00197     # old code, will become deprecated
00198     """ Synthesize data from XML description.
00199     """
00200     security = ClassSecurityInfo()
00201     security.setDefaultAccess('allow')
00202 
00203     def __init__(self, site, encoding=None):
00204 
00205         self._site = site
00206         self._encoding = encoding
00207 
00208     security.declareProtected(ManagePortal, 'parseXML')
00209     def parseXML(self, xml):
00210         """ Pseudo API.
00211         """
00212         reader = getattr(xml, 'read', None)
00213 
00214         if reader is not None:
00215             xml = reader()
00216 
00217         dom = parseString(xml)
00218         root = dom.documentElement
00219 
00220         return self._extractNode(root)
00221 
00222     def _extractNode(self, node):
00223         """ Please see docs/configurator.txt for information about the
00224         import mapping syntax.
00225         """
00226         nodes_map = self._getImportMapping()
00227         if node.nodeName not in nodes_map:
00228             nodes_map = self._getSharedImportMapping()
00229             if node.nodeName not in nodes_map:
00230                 raise ValueError('Unknown node: %s' % node.nodeName)
00231         node_map = nodes_map[node.nodeName]
00232         info = {}
00233 
00234         for name, val in node.attributes.items():
00235             key = node_map[name].get( KEY, str(name) )
00236             val = self._encoding and val.encode(self._encoding) or val
00237             info[key] = val
00238 
00239         for child in node.childNodes:
00240             name = child.nodeName
00241 
00242             if name == '#comment':
00243                 continue
00244 
00245             if not name == '#text':
00246                 key = node_map[name].get(KEY, str(name) )
00247                 info[key] = info.setdefault( key, () ) + (
00248                                                     self._extractNode(child),)
00249 
00250             elif '#text' in node_map:
00251                 key = node_map['#text'].get(KEY, 'value')
00252                 val = child.nodeValue.lstrip()
00253                 val = self._encoding and val.encode(self._encoding) or val
00254                 info[key] = info.setdefault(key, '') + val
00255 
00256         for k, v in node_map.items():
00257             key = v.get(KEY, k)
00258 
00259             if DEFAULT in v and not key in info:
00260                 if isinstance( v[DEFAULT], basestring ):
00261                     info[key] = v[DEFAULT] % info
00262                 else:
00263                     info[key] = v[DEFAULT]
00264 
00265             elif CONVERTER in v and key in info:
00266                 info[key] = v[CONVERTER]( info[key] )
00267 
00268             if key is None:
00269                 info = info[key]
00270 
00271         return info
00272 
00273     def _getSharedImportMapping(self):
00274 
00275         return {
00276           'object':
00277             { 'i18n:domain':     {},
00278               'name':            {KEY: 'id'},
00279               'meta_type':       {},
00280               'insert-before':   {},
00281               'insert-after':    {},
00282               'property':        {KEY: 'properties', DEFAULT: ()},
00283               'object':          {KEY: 'objects', DEFAULT: ()},
00284               'xmlns:i18n':      {} },
00285           'property':
00286             { 'name':            {KEY: 'id'},
00287               '#text':           {KEY: 'value', DEFAULT: ''},
00288               'element':         {KEY: 'elements', DEFAULT: ()},
00289               'type':            {},
00290               'select_variable': {},
00291               'i18n:translate':  {} },
00292           'element':
00293             { 'value':           {KEY: None} },
00294           'description':
00295             { '#text':           {KEY: None, DEFAULT: ''} } }
00296 
00297     def _convertToBoolean(self, val):
00298 
00299         return val.lower() in ('true', 'yes', '1')
00300 
00301     def _convertToUnique(self, val):
00302 
00303         assert len(val) == 1
00304         return val[0]
00305 
00306 InitializeClass(ImportConfiguratorBase)
00307 
00308 
00309 class ExportConfiguratorBase(Implicit):
00310     # old code, will become deprecated
00311     """ Synthesize XML description.
00312     """
00313     security = ClassSecurityInfo()
00314     security.setDefaultAccess('allow')
00315 
00316     def __init__(self, site, encoding=None):
00317 
00318         self._site = site
00319         self._encoding = encoding
00320         self._template = self._getExportTemplate()
00321 
00322     security.declareProtected(ManagePortal, 'generateXML')
00323     def generateXML(self, **kw):
00324         """ Pseudo API.
00325         """
00326         return self._template(**kw)
00327 
00328 InitializeClass(ExportConfiguratorBase)
00329 
00330 #
00331 ##############################################################################
00332 
00333 class _LineWrapper:
00334 
00335     def __init__(self, writer, indent, addindent, newl, max):
00336         self._writer = writer
00337         self._indent = indent
00338         self._addindent = addindent
00339         self._newl = newl
00340         self._max = max
00341         self._length = 0
00342         self._queue = self._indent
00343 
00344     def queue(self, text):
00345         self._queue += text
00346 
00347     def write(self, text='', enforce=False):
00348         self._queue += text
00349 
00350         if 0 < self._length > self._max - len(self._queue):
00351             self._writer.write(self._newl)
00352             self._length = 0
00353             self._queue = '%s%s %s' % (self._indent, self._addindent,
00354                                        self._queue)
00355 
00356         if self._queue != self._indent:
00357             self._writer.write(self._queue)
00358             self._length += len(self._queue)
00359             self._queue = ''
00360 
00361         if 0 < self._length and enforce:
00362             self._writer.write(self._newl)
00363             self._length = 0
00364             self._queue = self._indent
00365 
00366 
00367 class _Element(Element):
00368 
00369     """minidom element with 'pretty' XML output.
00370     """
00371 
00372     def writexml(self, writer, indent="", addindent="", newl=""):
00373         # indent = current indentation
00374         # addindent = indentation to add to higher levels
00375         # newl = newline string
00376         wrapper = _LineWrapper(writer, indent, addindent, newl, 78)
00377         wrapper.write('<%s' % self.tagName)
00378 
00379         # move 'name', 'meta_type' and 'title' to the top, sort the rest 
00380         attrs = self._get_attributes()
00381         a_names = attrs.keys()
00382         a_names.sort()
00383         if 'title' in a_names:
00384             a_names.remove('title')
00385             a_names.insert(0, 'title')
00386         if 'meta_type' in a_names:
00387             a_names.remove('meta_type')
00388             a_names.insert(0, 'meta_type')
00389         if 'name' in a_names:
00390             a_names.remove('name')
00391             a_names.insert(0, 'name')
00392 
00393         for a_name in a_names:
00394             wrapper.write()
00395             a_value = escape(attrs[a_name].value.encode('utf-8'), quote=True)
00396             wrapper.queue(' %s="%s"' % (a_name, a_value))
00397 
00398         if self.childNodes:
00399             wrapper.queue('>')
00400             for node in self.childNodes:
00401                 if node.nodeType == Node.TEXT_NODE:
00402                     data = escape(node.data.encode('utf-8'))
00403                     textlines = data.splitlines()
00404                     if textlines:
00405                         wrapper.queue(textlines.pop(0))
00406                     if textlines:
00407                         for textline in textlines:
00408                             wrapper.write('', True)
00409                             wrapper.queue('%s%s' % (addindent, textline))
00410                 else:
00411                     wrapper.write('', True)
00412                     node.writexml(writer, indent+addindent, addindent, newl)
00413             wrapper.write('</%s>' % self.tagName, True)
00414         else:
00415             wrapper.write('/>', True)
00416 
00417 
00418 class PrettyDocument(Document):
00419 
00420     """minidom document with 'pretty' XML output.
00421     """
00422 
00423     def createElement(self, tagName):
00424         e = _Element(tagName)
00425         e.ownerDocument = self
00426         return e
00427 
00428     def createElementNS(self, namespaceURI, qualifiedName):
00429         prefix, localName = _nssplit(qualifiedName)
00430         e = _Element(qualifiedName, namespaceURI, prefix)
00431         e.ownerDocument = self
00432         return e
00433 
00434     def writexml(self, writer, indent="", addindent="", newl="",
00435                  encoding = None):
00436         if encoding is None:
00437             writer.write('<?xml version="1.0"?>\n')
00438         else:
00439             writer.write('<?xml version="1.0" encoding="%s"?>\n' % encoding)
00440         for node in self.childNodes:
00441             node.writexml(writer, indent, addindent, newl)
00442 
00443 
00444 class NodeAdapterBase(object):
00445 
00446     """Node im- and exporter base.
00447     """
00448 
00449     implements(INode)
00450 
00451     _LOGGER_ID = ''
00452 
00453     def __init__(self, context, environ):
00454         self.context = context
00455         self.environ = environ
00456         self._logger = environ.getLogger(self._LOGGER_ID)
00457         self._doc = PrettyDocument()
00458 
00459     def _getObjectNode(self, name, i18n=True):
00460         node = self._doc.createElement(name)
00461         node.setAttribute('name', self.context.getId())
00462         node.setAttribute('meta_type', self.context.meta_type)
00463         i18n_domain = getattr(self.context, 'i18n_domain', None)
00464         if i18n and i18n_domain:
00465             node.setAttributeNS(I18NURI, 'i18n:domain', i18n_domain)
00466             self._i18n_props = ('title', 'description')
00467         return node
00468 
00469     def _getNodeText(self, node):
00470         text = ''
00471         for child in node.childNodes:
00472             if child.nodeName != '#text':
00473                 continue
00474             lines = [ line.lstrip() for line in child.nodeValue.splitlines() ]
00475             text += '\n'.join(lines)
00476         return text
00477 
00478     def _convertToBoolean(self, val):
00479         return val.lower() in ('true', 'yes', '1')
00480 
00481 
00482 class BodyAdapterBase(NodeAdapterBase):
00483 
00484     """Body im- and exporter base.
00485     """
00486 
00487     implementsOnly(IBody)
00488 
00489     def _exportSimpleNode(self):
00490         """Export the object as a DOM node.
00491         """
00492         if ISetupTool.providedBy(self.context):
00493             return None
00494         return self._getObjectNode('object', False)
00495 
00496     def _importSimpleNode(self, node):
00497         """Import the object from the DOM node.
00498         """
00499 
00500     node = property(_exportSimpleNode, _importSimpleNode)
00501 
00502     def _exportBody(self):
00503         """Export the object as a file body.
00504         """
00505         return ''
00506 
00507     def _importBody(self, body):
00508         """Import the object from the file body.
00509         """
00510 
00511     body = property(_exportBody, _importBody)
00512 
00513     mime_type = 'text/plain'
00514 
00515     name = ''
00516 
00517     suffix = ''
00518 
00519 
00520 class XMLAdapterBase(BodyAdapterBase):
00521 
00522     """XML im- and exporter base.
00523     """
00524 
00525     implementsOnly(IBody)
00526 
00527     def _exportBody(self):
00528         """Export the object as a file body.
00529         """
00530         self._doc.appendChild(self._exportNode())
00531         xml = self._doc.toprettyxml(' ')
00532         self._doc.unlink()
00533         return xml
00534 
00535     def _importBody(self, body):
00536         """Import the object from the file body.
00537         """
00538         try:
00539             dom = parseString(body)
00540         except ExpatError, e:
00541             filename = (self.filename or
00542                         '/'.join(self.context.getPhysicalPath()))
00543             raise ExpatError('%s: %s' % (filename, e))
00544         self._importNode(dom.documentElement)
00545 
00546     body = property(_exportBody, _importBody)
00547 
00548     mime_type = 'text/xml'
00549 
00550     name = ''
00551 
00552     suffix = '.xml'
00553 
00554     filename = '' # for error reporting during import
00555 
00556 
00557 class ObjectManagerHelpers(object):
00558 
00559     """ObjectManager im- and export helpers.
00560     """
00561 
00562     def _extractObjects(self):
00563         fragment = self._doc.createDocumentFragment()
00564         objects = self.context.objectValues()
00565         if not IOrderedContainer.providedBy(self.context):
00566             objects = list(objects)
00567             objects.sort(lambda x,y: cmp(x.getId(), y.getId()))
00568         for obj in objects:
00569             exporter = queryMultiAdapter((obj, self.environ), INode)
00570             if exporter:
00571                 node = exporter.node
00572                 if node is not None:
00573                     fragment.appendChild(exporter.node)
00574         return fragment
00575 
00576     def _purgeObjects(self):
00577         for obj_id, obj in self.context.objectItems():
00578             if ISetupTool.providedBy(obj):
00579                 continue
00580             self.context._delObject(obj_id)
00581 
00582     def _initObjects(self, node):
00583         for child in node.childNodes:
00584             if child.nodeName != 'object':
00585                 continue
00586             if child.hasAttribute('deprecated'):
00587                 continue
00588             parent = self.context
00589 
00590             obj_id = str(child.getAttribute('name'))
00591             if child.hasAttribute('remove'):
00592                 if obj_id in parent.objectIds():
00593                     parent._delObject(obj_id)
00594                 continue
00595 
00596             if obj_id not in parent.objectIds():
00597                 meta_type = str(child.getAttribute('meta_type'))
00598                 __traceback_info__ = obj_id, meta_type
00599                 for mt_info in Products.meta_types:
00600                     if mt_info['name'] == meta_type:
00601                         parent._setObject(obj_id, mt_info['instance'](obj_id))
00602                         break
00603                 else:
00604                     raise ValueError("unknown meta_type '%s'" % meta_type)
00605 
00606             if child.hasAttribute('insert-before'):
00607                 insert_before = child.getAttribute('insert-before')
00608                 if insert_before == '*':
00609                     parent.moveObjectsToTop(obj_id)
00610                 else:
00611                     try:
00612                         position = parent.getObjectPosition(insert_before)
00613                         if parent.getObjectPosition(obj_id) < position:
00614                             position -= 1
00615                         parent.moveObjectToPosition(obj_id, position)
00616                     except ValueError:
00617                         pass
00618             elif child.hasAttribute('insert-after'):
00619                 insert_after = child.getAttribute('insert-after')
00620                 if insert_after == '*':
00621                     parent.moveObjectsToBottom(obj_id)
00622                 else:
00623                     try:
00624                         position = parent.getObjectPosition(insert_after)
00625                         if parent.getObjectPosition(obj_id) < position:
00626                             position -= 1
00627                         parent.moveObjectToPosition(obj_id, position+1)
00628                     except ValueError:
00629                         pass
00630 
00631             obj = getattr(self.context, obj_id)
00632             importer = queryMultiAdapter((obj, self.environ), INode)
00633             if importer:
00634                 importer.node = child
00635 
00636 
00637 class PropertyManagerHelpers(object):
00638 
00639     """PropertyManager im- and export helpers.
00640     """
00641 
00642     _encoding = default_encoding
00643 
00644     def _extractProperties(self):
00645         fragment = self._doc.createDocumentFragment()
00646 
00647         for prop_map in self.context._propertyMap():
00648             prop_id = prop_map['id']
00649             if prop_id == 'i18n_domain':
00650                 continue
00651 
00652             # Don't export read-only nodes
00653             if 'w' not in prop_map.get('mode', 'wd'):
00654                 continue
00655 
00656             node = self._doc.createElement('property')
00657             node.setAttribute('name', prop_id)
00658 
00659             prop = self.context.getProperty(prop_id)
00660             if isinstance(prop, (tuple, list)):
00661                 for value in prop:
00662                     if isinstance(value, str):
00663                         value.decode(self._encoding)
00664                     child = self._doc.createElement('element')
00665                     child.setAttribute('value', value)
00666                     node.appendChild(child)
00667             else:
00668                 if prop_map.get('type') == 'boolean':
00669                     prop = unicode(bool(prop))
00670                 elif isinstance(prop, str):
00671                     prop = prop.decode(self._encoding)
00672                 elif not isinstance(prop, basestring):
00673                     prop = unicode(prop)
00674                 child = self._doc.createTextNode(prop)
00675                 node.appendChild(child)
00676 
00677             if 'd' in prop_map.get('mode', 'wd') and not prop_id == 'title':
00678                 prop_type = prop_map.get('type', 'string')
00679                 node.setAttribute('type', unicode(prop_type))
00680                 select_variable = prop_map.get('select_variable', None)
00681                 if select_variable is not None:
00682                     node.setAttribute('select_variable', select_variable)
00683 
00684             if hasattr(self, '_i18n_props') and prop_id in self._i18n_props:
00685                 node.setAttribute('i18n:translate', '')
00686 
00687             fragment.appendChild(node)
00688 
00689         return fragment
00690 
00691     def _purgeProperties(self):
00692         for prop_map in self.context._propertyMap():
00693             mode = prop_map.get('mode', 'wd')
00694             if 'w' not in mode:
00695                 continue
00696             prop_id = prop_map['id']
00697             if 'd' in mode and not prop_id == 'title':
00698                 self.context._delProperty(prop_id)
00699             else:
00700                 prop_type = prop_map.get('type')
00701                 if prop_type == 'multiple selection':
00702                     prop_value = ()
00703                 elif prop_type in ('int', 'float'):
00704                     prop_value = 0
00705                 else:
00706                     prop_value = ''
00707                 self.context._updateProperty(prop_id, prop_value)
00708 
00709     def _initProperties(self, node):
00710         obj = self.context
00711         if node.hasAttribute('i18n:domain'):
00712             i18n_domain = str(node.getAttribute('i18n:domain'))
00713             obj._updateProperty('i18n_domain', i18n_domain)
00714         for child in node.childNodes:
00715             if child.nodeName != 'property':
00716                 continue
00717             prop_id = str(child.getAttribute('name'))
00718             prop_map = obj.propdict().get(prop_id, None)
00719 
00720             if prop_map is None:
00721                 if child.hasAttribute('type'):
00722                     val = str(child.getAttribute('select_variable'))
00723                     prop_type = str(child.getAttribute('type'))
00724                     obj._setProperty(prop_id, val, prop_type)
00725                     prop_map = obj.propdict().get(prop_id, None)
00726                 else:
00727                     raise ValueError("undefined property '%s'" % prop_id)
00728 
00729             if not 'w' in prop_map.get('mode', 'wd'):
00730                 raise BadRequest('%s cannot be changed' % prop_id)
00731 
00732             elements = []
00733             for sub in child.childNodes:
00734                 if sub.nodeName == 'element':
00735                     value = sub.getAttribute('value')
00736                     elements.append(value.encode(self._encoding))
00737 
00738             if elements or prop_map.get('type') == 'multiple selection':
00739                 prop_value = tuple(elements) or ()
00740             elif prop_map.get('type') == 'boolean':
00741                 prop_value = self._convertToBoolean(self._getNodeText(child))
00742             else:
00743                 # if we pass a *string* to _updateProperty, all other values
00744                 # are converted to the right type
00745                 prop_value = self._getNodeText(child).encode(self._encoding)
00746 
00747             if not self._convertToBoolean(child.getAttribute('purge')
00748                                           or 'True'):
00749                 # If the purge attribute is False, merge sequences
00750                 prop = obj.getProperty(prop_id)
00751                 if isinstance(prop, (tuple, list)):
00752                     prop_value = (tuple([p for p in prop
00753                                          if p not in prop_value]) +
00754                                   tuple(prop_value))
00755 
00756             obj._updateProperty(prop_id, prop_value)
00757 
00758 
00759 class MarkerInterfaceHelpers(object):
00760 
00761     """Marker interface im- and export helpers.
00762     """
00763 
00764     def _extractMarkers(self):
00765         fragment = self._doc.createDocumentFragment()
00766         adapted = IMarkerInterfaces(self.context)
00767 
00768         for marker_id in adapted.getDirectlyProvidedNames():
00769             node = self._doc.createElement('marker')
00770             node.setAttribute('name', marker_id)
00771             fragment.appendChild(node)
00772 
00773         return fragment
00774 
00775     def _purgeMarkers(self):
00776         directlyProvides(self.context)
00777 
00778     def _initMarkers(self, node):
00779         markers = []
00780         adapted = IMarkerInterfaces(self.context)
00781 
00782         for child in node.childNodes:
00783             if child.nodeName != 'marker':
00784                 continue
00785             markers.append(str(child.getAttribute('name')))
00786 
00787         adapted.update(adapted.dottedToInterfaces(markers))
00788 
00789 
00790 def exportObjects(obj, parent_path, context):
00791     """ Export subobjects recursively.
00792     """
00793     exporter = queryMultiAdapter((obj, context), IBody)
00794     path = '%s%s' % (parent_path, obj.getId().replace(' ', '_'))
00795     if exporter:
00796         if exporter.name:
00797             path = '%s%s' % (parent_path, exporter.name)
00798         filename = '%s%s' % (path, exporter.suffix)
00799         body = exporter.body
00800         if body is not None:
00801             context.writeDataFile(filename, body, exporter.mime_type)
00802 
00803     if getattr(obj, 'objectValues', False):
00804         for sub in obj.objectValues():
00805             exportObjects(sub, path+'/', context)
00806 
00807 def importObjects(obj, parent_path, context):
00808     """ Import subobjects recursively.
00809     """
00810     importer = queryMultiAdapter((obj, context), IBody)
00811     path = '%s%s' % (parent_path, obj.getId().replace(' ', '_'))
00812     __traceback_info__ = path
00813     if importer:
00814         if importer.name:
00815             path = '%s%s' % (parent_path, importer.name)
00816         filename = '%s%s' % (path, importer.suffix)
00817         body = context.readDataFile(filename)
00818         if body is not None:
00819             importer.filename = filename # for error reporting
00820             importer.body = body
00821 
00822     if getattr(obj, 'objectValues', False):
00823         for sub in obj.objectValues():
00824             importObjects(sub, path+'/', context)
00825 
00826 
00827 def _computeTopologicalSort( steps ):
00828     result = []
00829     graph = [ ( x[ 'id' ], x[ 'dependencies' ] ) for x in steps ]
00830 
00831     unresolved = []
00832 
00833     while 1:
00834         for node, edges in graph:
00835 
00836             after = -1
00837             resolved = 0
00838 
00839             for edge in edges:
00840 
00841                 if edge in result:
00842                     resolved += 1
00843                     after = max( after, result.index( edge ) )
00844             
00845             if len(edges) > resolved:
00846                 unresolved.append((node, edges))
00847             else:
00848                 result.insert( after + 1, node )
00849 
00850         if not unresolved:
00851             break
00852         if len(unresolved) == len(graph):
00853             # Nothing was resolved in this loop. There must be circular or
00854             # missing dependencies. Just add them to the end. We can't
00855             # raise an error, because checkComplete relies on this method.
00856             for node, edges in unresolved:
00857                 result.append(node)
00858             break
00859         graph = unresolved
00860         unresolved = []
00861     
00862     return result
00863 
00864 def _getProductPath(product_name):
00865 
00866     """ Return the absolute path of the product's directory.
00867     """
00868     try:
00869         # BBB: for GenericSetup 1.1 style product names
00870         product = __import__('Products.%s' % product_name
00871                             , globals(), {}, ['initialize'])
00872     except ImportError:
00873         try:
00874             product = __import__(product_name
00875                                 , globals(), {}, ['initialize'])
00876         except ImportError:
00877             raise ValueError('Not a valid product name: %s'
00878                              % product_name)
00879 
00880     return product.__path__[0]
00881