Back to index

plone3  3.1.7
exportimport.py
Go to the documentation of this file.
00001 ##############################################################################
00002 #
00003 # Copyright (c) 2005 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 """DCWorkflow export / import support.
00014 
00015 $Id: exportimport.py 80705 2007-10-08 07:41:41Z rossp $
00016 """
00017 
00018 import re
00019 from xml.dom.minidom import parseString
00020 
00021 from Expression import Expression
00022 from AccessControl import ClassSecurityInfo
00023 from Acquisition import Implicit
00024 from Globals import InitializeClass
00025 from Products.PageTemplates.PageTemplateFile import PageTemplateFile
00026 from zope.component import adapts
00027 
00028 from Products.GenericSetup.interfaces import ISetupEnviron
00029 from Products.GenericSetup.utils import BodyAdapterBase
00030 
00031 from utils import _xmldir
00032 from DCWorkflow import DCWorkflowDefinition
00033 from interfaces import IDCWorkflowDefinition
00034 from permissions import ManagePortal
00035 
00036 
00037 TRIGGER_TYPES = ( 'AUTOMATIC', 'USER' )
00038 _FILENAME = 'workflows.xml'
00039 
00040 
00041 class DCWorkflowDefinitionBodyAdapter(BodyAdapterBase):
00042 
00043     """Body im- and exporter for DCWorkflowDefinition.
00044     """
00045 
00046     adapts(IDCWorkflowDefinition, ISetupEnviron)
00047 
00048     def _exportBody(self):
00049         """Export the object as a file body.
00050         """
00051         wfdc = WorkflowDefinitionConfigurator(self.context)
00052         return wfdc.__of__(self.context).generateWorkflowXML()
00053 
00054     def _importBody(self, body):
00055         """Import the object from the file body.
00056         """
00057         encoding = 'utf-8'
00058         wfdc = WorkflowDefinitionConfigurator(self.context)
00059 
00060         ( workflow_id
00061         , title
00062         , state_variable
00063         , initial_state
00064         , states
00065         , transitions
00066         , variables
00067         , worklists
00068         , permissions
00069         , scripts
00070         , description
00071         ) = wfdc.parseWorkflowXML(body, encoding)
00072 
00073         _initDCWorkflow( self.context
00074                        , title
00075                        , description
00076                        , state_variable
00077                        , initial_state
00078                        , states
00079                        , transitions
00080                        , variables
00081                        , worklists
00082                        , permissions
00083                        , scripts
00084                        , self.environ
00085                        )
00086 
00087     body = property(_exportBody, _importBody)
00088 
00089     mime_type = 'text/xml'
00090 
00091     suffix = '/definition.xml'
00092 
00093 
00094 class WorkflowDefinitionConfigurator( Implicit ):
00095     """ Synthesize XML description of site's workflows.
00096     """
00097     security = ClassSecurityInfo()
00098 
00099     def __init__(self, obj):
00100         self._obj = obj
00101 
00102     security.declareProtected( ManagePortal, 'getWorkflowInfo' )
00103     def getWorkflowInfo( self, workflow_id ):
00104 
00105         """ Return a mapping describing a given workflow.
00106 
00107         o Keys in the mappings:
00108 
00109           'id' -- the ID of the workflow within the tool
00110 
00111           'meta_type' -- the workflow's meta_type
00112 
00113           'title' -- the workflow's title property
00114 
00115           'description' -- the workflow's description property
00116 
00117         o See '_extractDCWorkflowInfo' below for keys present only for
00118           DCWorkflow definitions.
00119 
00120         """
00121         workflow = self._obj
00122 
00123         workflow_info = { 'id'          : workflow_id
00124                         , 'meta_type'   : workflow.meta_type
00125                         , 'title'       : workflow.title_or_id()
00126                         , 'description' : workflow.description
00127                         }
00128 
00129         if workflow.meta_type == DCWorkflowDefinition.meta_type:
00130             self._extractDCWorkflowInfo( workflow, workflow_info )
00131 
00132         return workflow_info
00133 
00134     security.declareProtected( ManagePortal, 'generateWorkflowXML' )
00135     def generateWorkflowXML(self):
00136         """ Pseudo API.
00137         """
00138         return self._workflowConfig(workflow_id=self._obj.getId())
00139 
00140     security.declareProtected( ManagePortal, 'getWorkflowScripts' )
00141     def getWorkflowScripts(self):
00142         """ Get workflow scripts information
00143         """
00144         return self._extractScripts(self._obj)
00145 
00146     security.declareProtected( ManagePortal, 'parseWorkflowXML' )
00147     def parseWorkflowXML( self, xml, encoding=None ):
00148         """ Pseudo API.
00149         """
00150         dom = parseString( xml )
00151 
00152         root = dom.getElementsByTagName( 'dc-workflow' )[ 0 ]
00153 
00154         workflow_id = _getNodeAttribute( root, 'workflow_id', encoding )
00155         title = _getNodeAttribute( root, 'title', encoding )
00156         try:
00157             description = _getNodeAttribute( root, 'description', encoding )
00158         except ValueError:
00159             # Don't fail on export files that do not have the description field!
00160             description = ''
00161         state_variable = _getNodeAttribute( root, 'state_variable', encoding )
00162         initial_state = _getNodeAttribute( root, 'initial_state', encoding )
00163 
00164         states = _extractStateNodes( root, encoding )
00165         transitions = _extractTransitionNodes( root, encoding )
00166         variables = _extractVariableNodes( root, encoding )
00167         worklists = _extractWorklistNodes( root, encoding )
00168         permissions = _extractPermissionNodes( root, encoding )
00169         scripts = _extractScriptNodes( root, encoding )
00170 
00171         return ( workflow_id
00172                , title
00173                , state_variable
00174                , initial_state
00175                , states
00176                , transitions
00177                , variables
00178                , worklists
00179                , permissions
00180                , scripts
00181                , description
00182                )
00183 
00184     security.declarePrivate( '_workflowConfig' )
00185     _workflowConfig = PageTemplateFile( 'wtcWorkflowExport.xml'
00186                                       , _xmldir
00187                                       , __name__='workflowConfig'
00188                                       )
00189 
00190     security.declarePrivate( '_extractDCWorkflowInfo' )
00191     def _extractDCWorkflowInfo( self, workflow, workflow_info ):
00192 
00193         """ Append the information for a 'workflow' into 'workflow_info'
00194 
00195         o 'workflow' must be a DCWorkflowDefinition instance.
00196 
00197         o 'workflow_info' must be a dictionary.
00198 
00199         o The following keys will be added to 'workflow_info':
00200 
00201           'permissions' -- a list of names of permissions managed
00202             by the workflow
00203 
00204           'state_variable' -- the name of the workflow's "main"
00205             state variable
00206 
00207           'initial_state' -- the name of the state in the workflow
00208             in which objects start their lifecycle.
00209 
00210           'variable_info' -- a list of mappings describing the
00211             variables tracked by the workflow (see '_extractVariables').
00212 
00213           'state_info' -- a list of mappings describing the
00214             states tracked by the workflow (see '_extractStates').
00215 
00216           'transition_info' -- a list of mappings describing the
00217             transitions tracked by the workflow (see '_extractTransitions').
00218 
00219           'worklist_info' -- a list of mappings describing the
00220             worklists tracked by the workflow (see '_extractWorklists').
00221 
00222           'script_info' -- a list of mappings describing the scripts which
00223             provide added business logic (see '_extractScripts').
00224         """
00225         workflow_info[ 'state_variable' ] = workflow.state_var
00226         workflow_info[ 'initial_state' ] = workflow.initial_state
00227         workflow_info[ 'permissions' ] = workflow.permissions
00228         workflow_info[ 'variable_info' ] = self._extractVariables( workflow )
00229         workflow_info[ 'state_info' ] = self._extractStates( workflow )
00230         workflow_info[ 'transition_info' ] = self._extractTransitions(
00231                                                                    workflow )
00232         workflow_info[ 'worklist_info' ] = self._extractWorklists( workflow )
00233         workflow_info[ 'script_info' ] = self._extractScripts( workflow )
00234 
00235     security.declarePrivate( '_extractVariables' )
00236     def _extractVariables( self, workflow ):
00237 
00238         """ Return a sequence of mappings describing DCWorkflow variables.
00239 
00240         o Keys for each mapping will include:
00241 
00242           'id' -- the variable's ID
00243 
00244           'description' -- a textual description of the variable
00245 
00246           'for_catalog' -- whether to catalog this variable
00247 
00248           'for_status' -- whether to ??? this variable (XXX)
00249 
00250           'update_always' -- whether to update this variable whenever
00251             executing a transition (xxX)
00252 
00253           'default_value' -- a default value for the variable (XXX)
00254 
00255           'default_expression' -- a TALES expression for the default value
00256 
00257           'guard_permissions' -- a list of permissions guarding access
00258             to the variable
00259 
00260           'guard_roles' -- a list of roles guarding access
00261             to the variable
00262 
00263           'guard_groups' -- a list of groups guarding the transition
00264 
00265           'guard_expr' -- an expression guarding access to the variable
00266         """
00267         result = []
00268 
00269         items = workflow.variables.objectItems()
00270         items.sort()
00271 
00272         for k, v in items:
00273 
00274             guard = v.getInfoGuard()
00275 
00276             default_type = _guessVariableType( v.default_value )
00277 
00278             info = { 'id'                   : k
00279                    , 'description'          : v.description
00280                    , 'for_catalog'          : bool( v.for_catalog )
00281                    , 'for_status'           : bool( v.for_status )
00282                    , 'update_always'        : bool( v.update_always )
00283                    , 'default_value'        : v.default_value
00284                    , 'default_type'         : default_type
00285                    , 'default_expr'         : v.getDefaultExprText()
00286                    , 'guard_permissions'    : guard.permissions
00287                    , 'guard_roles'          : guard.roles
00288                    , 'guard_groups'         : guard.groups
00289                    , 'guard_expr'           : guard.getExprText()
00290                    }
00291 
00292             result.append( info )
00293 
00294         return result
00295 
00296     security.declarePrivate( '_extractStates' )
00297     def _extractStates( self, workflow ):
00298 
00299         """ Return a sequence of mappings describing DCWorkflow states.
00300 
00301         o Within the workflow mapping, each 'state_info' mapping has keys:
00302 
00303           'id' -- the state's ID
00304 
00305           'title' -- the state's title
00306 
00307           'description' -- the state's description
00308 
00309           'transitions' -- a list of IDs of transitions out of the state
00310 
00311           'permissions' -- a list of mappings describing the permission
00312             map for the state
00313 
00314           'groups' -- a list of ( group_id, (roles,) ) tuples describing the
00315             group-role assignments for the state
00316 
00317           'variables' -- a list of mapping for the variables
00318             to be set when entering the state.
00319 
00320         o Within the state_info mappings, each 'permissions' mapping
00321           has the keys:
00322 
00323           'name' -- the name of the permission
00324 
00325           'roles' -- a sequence of role IDs which have the permission
00326 
00327           'acquired' -- whether roles are acquired for the permission
00328 
00329         o Within the state_info mappings, each 'variable' mapping
00330           has the keys:
00331 
00332           'name' -- the name of the variable
00333 
00334           'type' -- the type of the value (allowed values are:
00335                     'string', 'datetime', 'bool', 'int')
00336 
00337           'value' -- the value to be set
00338         """
00339         result = []
00340 
00341         items = workflow.states.objectItems()
00342         items.sort()
00343 
00344         for k, v in items:
00345 
00346             groups = v.group_roles and list( v.group_roles.items() ) or []
00347             groups = [ x for x in groups if x[1] ]
00348             groups.sort()
00349 
00350             variables = list( v.getVariableValues() )
00351             variables.sort()
00352 
00353             v_info = []
00354 
00355             for v_name, value in variables:
00356                 v_info.append( { 'name' : v_name
00357                                , 'type' :_guessVariableType( value )
00358                                , 'value' : value
00359                                } )
00360 
00361             info = { 'id'           : k
00362                    , 'title'        : v.title
00363                    , 'description'  : v.description
00364                    , 'transitions'  : v.transitions
00365                    , 'permissions'  : self._extractStatePermissions( v )
00366                    , 'groups'       : groups
00367                    , 'variables'    : v_info
00368                    }
00369 
00370             result.append( info )
00371 
00372         return result
00373 
00374     security.declarePrivate( '_extractStatePermissions' )
00375     def _extractStatePermissions( self, state ):
00376 
00377         """ Return a sequence of mappings for the permissions in a state.
00378 
00379         o Each mapping has the keys:
00380 
00381           'name' -- the name of the permission
00382 
00383           'roles' -- a sequence of role IDs which have the permission
00384 
00385           'acquired' -- whether roles are acquired for the permission
00386         """
00387         result = []
00388         perm_roles = state.permission_roles
00389 
00390         if perm_roles:
00391             items = state.permission_roles.items()
00392             items.sort()
00393 
00394             for k, v in items:
00395 
00396                 result.append( { 'name' : k
00397                                , 'roles' : v
00398                                , 'acquired' : not isinstance( v, tuple )
00399                                } )
00400 
00401         return result
00402 
00403 
00404     security.declarePrivate( '_extractTransitions' )
00405     def _extractTransitions( self, workflow ):
00406 
00407         """ Return a sequence of mappings describing DCWorkflow transitions.
00408 
00409         o Each mapping has the keys:
00410 
00411           'id' -- the transition's ID
00412 
00413           'title' -- the transition's ID
00414 
00415           'description' -- the transition's description
00416 
00417           'new_state_id' -- the ID of the state into which the transition
00418             moves an object
00419 
00420           'trigger_type' -- one of the following values, indicating how the
00421             transition is fired:
00422 
00423             - "AUTOMATIC" -> fired opportunistically whenever the workflow
00424                notices that its guard conditions permit
00425 
00426             - "USER" -> fired in response to user request
00427 
00428           'script_name' -- the ID of a script to be executed before
00429              the transition
00430 
00431           'after_script_name' -- the ID of a script to be executed after
00432              the transition
00433 
00434           'actbox_name' -- the name of the action by which the user
00435              triggers the transition
00436 
00437           'actbox_url' -- the URL of the action by which the user
00438              triggers the transition
00439 
00440           'actbox_category' -- the category of the action by which the user
00441              triggers the transition
00442 
00443           'variables' -- a list of ( id, expr ) tuples defining how variables
00444             are to be set during the transition
00445 
00446           'guard_permissions' -- a list of permissions guarding the transition
00447 
00448           'guard_roles' -- a list of roles guarding the transition
00449 
00450           'guard_groups' -- a list of groups guarding the transition
00451 
00452           'guard_expr' -- an expression guarding the transition
00453 
00454         """
00455         result = []
00456 
00457         items = workflow.transitions.objectItems()
00458         items.sort()
00459 
00460         for k, v in items:
00461 
00462             guard = v.getGuard()
00463 
00464             v_info = []
00465 
00466             for v_name, expr in v.getVariableExprs():
00467                 v_info.append( { 'name' : v_name, 'expr' : expr } )
00468 
00469             info = { 'id'                   : k
00470                    , 'title'                : v.title
00471                    , 'description'          : v.description
00472                    , 'new_state_id'         : v.new_state_id
00473                    , 'trigger_type'         : TRIGGER_TYPES[ v.trigger_type ]
00474                    , 'script_name'          : v.script_name
00475                    , 'after_script_name'    : v.after_script_name
00476                    , 'actbox_name'          : v.actbox_name
00477                    , 'actbox_url'           : v.actbox_url
00478                    , 'actbox_category'      : v.actbox_category
00479                    , 'variables'            : v_info
00480                    , 'guard_permissions'    : guard.permissions
00481                    , 'guard_roles'          : guard.roles
00482                    , 'guard_groups'         : guard.groups
00483                    , 'guard_expr'           : guard.getExprText()
00484                    }
00485 
00486             result.append( info )
00487 
00488         return result
00489 
00490     security.declarePrivate( '_extractWorklists' )
00491     def _extractWorklists( self, workflow ):
00492 
00493         """ Return a sequence of mappings describing DCWorkflow transitions.
00494 
00495         o Each mapping has the keys:
00496 
00497           'id' -- the ID of the worklist
00498 
00499           'title' -- the title of the worklist
00500 
00501           'description' -- a textual description of the worklist
00502 
00503           'var_match' -- a list of ( key, value ) tuples defining
00504             the variables used to "activate" the worklist.
00505 
00506           'actbox_name' -- the name of the "action" corresponding to the
00507             worklist
00508 
00509           'actbox_url' -- the URL of the "action" corresponding to the
00510             worklist
00511 
00512           'actbox_category' -- the category of the "action" corresponding
00513             to the worklist
00514 
00515           'guard_permissions' -- a list of permissions guarding access
00516             to the worklist
00517 
00518           'guard_roles' -- a list of roles guarding access
00519             to the worklist
00520 
00521           'guard_expr' -- an expression guarding access to the worklist
00522 
00523         """
00524         result = []
00525 
00526         items = workflow.worklists.objectItems()
00527         items.sort()
00528 
00529         for k, v in items:
00530 
00531             guard = v.getGuard()
00532 
00533             var_match = [ ( id, v.getVarMatchText( id ) )
00534                             for id in v.getVarMatchKeys() ]
00535 
00536             info = { 'id'                   : k
00537                    , 'title'                : v.title
00538                    , 'description'          : v.description
00539                    , 'var_match'            : var_match
00540                    , 'actbox_name'          : v.actbox_name
00541                    , 'actbox_url'           : v.actbox_url
00542                    , 'actbox_category'      : v.actbox_category
00543                    , 'guard_permissions'    : guard.permissions
00544                    , 'guard_roles'          : guard.roles
00545                    , 'guard_groups'         : guard.groups
00546                    , 'guard_expr'           : guard.getExprText()
00547                    }
00548 
00549             result.append( info )
00550 
00551         return result
00552 
00553     security.declarePrivate( '_extractScripts' )
00554     def _extractScripts( self, workflow ):
00555 
00556         """ Return a sequence of mappings describing DCWorkflow scripts.
00557 
00558         o Each mapping has the keys:
00559 
00560           'id' -- the ID of the script
00561 
00562           'meta_type' -- the title of the worklist
00563 
00564           'body' -- the text of the script (only applicable to scripts
00565             of type Script (Python))
00566 
00567           'module' -- The module from where to load the function (only
00568             applicable to External Method scripts)
00569 
00570           'function' -- The function to load from the 'module' given
00571             (Only applicable to External Method scripts)
00572 
00573           'filename' -- the name of the file to / from which the script
00574             is stored / loaded (Script (Python) only)
00575         """
00576         result = []
00577 
00578         items = workflow.scripts.objectItems()
00579         items.sort()
00580 
00581         for k, v in items:
00582 
00583             filename = _getScriptFilename( workflow.getId(), k, v.meta_type )
00584             module = ''
00585             function = ''
00586 
00587             if v.meta_type == 'External Method':
00588                 module = v.module()
00589                 function = v.function()
00590 
00591             info = { 'id'                   : k
00592                    , 'meta_type'            : v.meta_type
00593                    , 'module'               : module
00594                    , 'function'             : function
00595                    , 'filename'             : filename
00596                    }
00597 
00598             result.append( info )
00599 
00600         return result
00601 
00602 InitializeClass( WorkflowDefinitionConfigurator )
00603 
00604 
00605 def _getScriptFilename( workflow_id, script_id, meta_type ):
00606 
00607     """ Return the name of the file which holds the script.
00608     """
00609     wf_dir = workflow_id.replace( ' ', '_' )
00610     suffix = _METATYPE_SUFFIXES.get(meta_type, None)
00611 
00612     if suffix is None:
00613         return ''
00614 
00615     return 'workflows/%s/scripts/%s.%s' % ( wf_dir, script_id, suffix )
00616 
00617 def _extractStateNodes( root, encoding=None ):
00618 
00619     result = []
00620 
00621     for s_node in root.getElementsByTagName( 'state' ):
00622 
00623         info = { 'state_id' : _getNodeAttribute( s_node, 'state_id', encoding )
00624                , 'title' : _getNodeAttribute( s_node, 'title', encoding )
00625                , 'description' : _extractDescriptionNode( s_node, encoding )
00626                }
00627 
00628         info[ 'transitions' ] = [ _getNodeAttribute( x, 'transition_id'
00629                                                    , encoding )
00630                                   for x in s_node.getElementsByTagName(
00631                                                         'exit-transition' ) ]
00632 
00633         info[ 'permissions' ] = permission_map = {}
00634 
00635         for p_map in s_node.getElementsByTagName( 'permission-map' ):
00636 
00637             name = _getNodeAttribute( p_map, 'name', encoding )
00638             acquired = _getNodeAttributeBoolean( p_map, 'acquired' )
00639 
00640             roles = [ _coalesceTextNodeChildren( x, encoding )
00641                         for x in p_map.getElementsByTagName(
00642                                             'permission-role' ) ]
00643 
00644             if not acquired:
00645                 roles = tuple( roles )
00646 
00647             permission_map[ name ] = roles
00648 
00649         info[ 'groups' ] = group_map = []
00650 
00651         for g_map in s_node.getElementsByTagName( 'group-map' ):
00652 
00653             name = _getNodeAttribute( g_map, 'name', encoding )
00654 
00655             roles = [ _coalesceTextNodeChildren( x, encoding )
00656                         for x in g_map.getElementsByTagName(
00657                                             'group-role' ) ]
00658 
00659             group_map.append( ( name, tuple( roles ) ) )
00660 
00661         info[ 'variables' ] = var_map = {}
00662 
00663         for assignment in s_node.getElementsByTagName( 'assignment' ):
00664 
00665             name = _getNodeAttribute( assignment, 'name', encoding )
00666             type_id = _getNodeAttribute( assignment, 'type', encoding )
00667             value = _coalesceTextNodeChildren( assignment, encoding )
00668 
00669             var_map[ name ] = { 'name'  : name
00670                               , 'type'  : type_id
00671                               , 'value' : value
00672                               }
00673 
00674         result.append( info )
00675 
00676     return result
00677 
00678 def _extractTransitionNodes( root, encoding=None ):
00679 
00680     result = []
00681 
00682     for t_node in root.getElementsByTagName( 'transition' ):
00683 
00684         info = { 'transition_id' : _getNodeAttribute( t_node, 'transition_id'
00685                                                     , encoding )
00686                , 'title' : _getNodeAttribute( t_node, 'title', encoding )
00687                , 'description' : _extractDescriptionNode( t_node, encoding )
00688                , 'new_state' : _getNodeAttribute( t_node, 'new_state'
00689                                                 , encoding )
00690                , 'trigger' : _getNodeAttribute( t_node, 'trigger', encoding )
00691                , 'before_script' : _getNodeAttribute( t_node, 'before_script'
00692                                                   , encoding )
00693                , 'after_script' : _getNodeAttribute( t_node, 'after_script'
00694                                                    , encoding )
00695                , 'action' : _extractActionNode( t_node, encoding )
00696                , 'guard' : _extractGuardNode( t_node, encoding )
00697                }
00698 
00699         info[ 'variables' ] = var_map = {}
00700 
00701         for assignment in t_node.getElementsByTagName( 'assignment' ):
00702 
00703             name = _getNodeAttribute( assignment, 'name', encoding )
00704             expr = _coalesceTextNodeChildren( assignment, encoding )
00705             var_map[ name ] = expr
00706 
00707         result.append( info )
00708 
00709     return result
00710 
00711 def _extractVariableNodes( root, encoding=None ):
00712 
00713     result = []
00714 
00715     for v_node in root.getElementsByTagName( 'variable' ):
00716 
00717         info = { 'variable_id' : _getNodeAttribute( v_node, 'variable_id'
00718                                                     , encoding )
00719                , 'description' : _extractDescriptionNode( v_node, encoding )
00720                , 'for_catalog' : _getNodeAttributeBoolean( v_node
00721                                                          , 'for_catalog'
00722                                                          )
00723                , 'for_status' : _getNodeAttributeBoolean( v_node
00724                                                         , 'for_status'
00725                                                         )
00726                , 'update_always' : _getNodeAttributeBoolean( v_node
00727                                                            , 'update_always'
00728                                                            )
00729                , 'default' : _extractDefaultNode( v_node, encoding )
00730                , 'guard' : _extractGuardNode( v_node, encoding )
00731                }
00732 
00733         result.append( info )
00734 
00735     return result
00736 
00737 def _extractWorklistNodes( root, encoding=None ):
00738 
00739     result = []
00740 
00741     for w_node in root.getElementsByTagName( 'worklist' ):
00742 
00743         info = { 'worklist_id' : _getNodeAttribute( w_node, 'worklist_id'
00744                                                     , encoding )
00745                , 'title' : _getNodeAttribute( w_node, 'title' , encoding )
00746                , 'description' : _extractDescriptionNode( w_node, encoding )
00747                , 'match' : _extractMatchNode( w_node, encoding )
00748                , 'action' : _extractActionNode( w_node, encoding )
00749                , 'guard' : _extractGuardNode( w_node, encoding )
00750                }
00751 
00752         result.append( info )
00753 
00754     return result
00755 
00756 def _extractScriptNodes( root, encoding=None ):
00757 
00758     result = []
00759 
00760     for s_node in root.getElementsByTagName( 'script' ):
00761 
00762         try:
00763             function = _getNodeAttribute( s_node, 'function' )
00764         except ValueError:
00765             function = ''
00766 
00767         try:
00768             module = _getNodeAttribute( s_node, 'module' )
00769         except ValueError:
00770             module = ''
00771 
00772         info = { 'script_id' : _getNodeAttribute( s_node, 'script_id' )
00773                , 'meta_type' : _getNodeAttribute( s_node, 'type' , encoding )
00774                , 'function'  : function
00775                , 'module'    : module
00776                }
00777 
00778         filename = _queryNodeAttribute( s_node, 'filename' , None, encoding )
00779 
00780         if filename is not None:
00781             info[ 'filename' ] = filename
00782 
00783         result.append( info )
00784 
00785     return result
00786 
00787 def _extractPermissionNodes( root, encoding=None ):
00788 
00789     result = []
00790 
00791     for p_node in root.getElementsByTagName( 'permission' ):
00792 
00793         result.append( _coalesceTextNodeChildren( p_node, encoding ) )
00794 
00795     return result
00796 
00797 def _extractActionNode( parent, encoding=None ):
00798 
00799     nodes = parent.getElementsByTagName( 'action' )
00800     assert len( nodes ) <= 1, nodes
00801 
00802     if len( nodes ) < 1:
00803         return { 'name' : '', 'url' : '', 'category' : '' }
00804 
00805     node = nodes[ 0 ]
00806 
00807     return { 'name' : _coalesceTextNodeChildren( node, encoding )
00808            , 'url' : _getNodeAttribute( node, 'url', encoding )
00809            , 'category' : _getNodeAttribute( node, 'category', encoding )
00810            }
00811 
00812 def _extractGuardNode( parent, encoding=None ):
00813 
00814     nodes = parent.getElementsByTagName( 'guard' )
00815     assert len( nodes ) <= 1, nodes
00816 
00817     if len( nodes ) < 1:
00818         return { 'permissions' : (), 'roles' : (), 'groups' : (), 'expr' : '' }
00819 
00820     node = nodes[ 0 ]
00821 
00822     expr_nodes = node.getElementsByTagName( 'guard-expression' )
00823     assert( len( expr_nodes ) <= 1 )
00824 
00825     expr_text = expr_nodes and _coalesceTextNodeChildren( expr_nodes[ 0 ]
00826                                                         , encoding
00827                                                         ) or ''
00828 
00829     return { 'permissions' : [ _coalesceTextNodeChildren( x, encoding )
00830                                 for x in node.getElementsByTagName(
00831                                                     'guard-permission' ) ]
00832            , 'roles' : [ _coalesceTextNodeChildren( x, encoding )
00833                           for x in node.getElementsByTagName( 'guard-role' ) ]
00834            , 'groups' : [ _coalesceTextNodeChildren( x, encoding )
00835                           for x in node.getElementsByTagName( 'guard-group' ) ]
00836            , 'expression' : expr_text
00837            }
00838 
00839 def _extractDefaultNode( parent, encoding=None ):
00840 
00841     nodes = parent.getElementsByTagName( 'default' )
00842     assert len( nodes ) <= 1, nodes
00843 
00844     if len( nodes ) < 1:
00845         return { 'value' : '', 'expression' : '', 'type' : 'n/a' }
00846 
00847     node = nodes[ 0 ]
00848 
00849     value_nodes = node.getElementsByTagName( 'value' )
00850     assert( len( value_nodes ) <= 1 )
00851 
00852     value_type = 'n/a'
00853     if value_nodes:
00854         value_type = value_nodes[ 0 ].getAttribute( 'type' ) or 'n/a'
00855 
00856     value_text = value_nodes and _coalesceTextNodeChildren( value_nodes[ 0 ]
00857                                                           , encoding
00858                                                           ) or ''
00859 
00860     expr_nodes = node.getElementsByTagName( 'expression' )
00861     assert( len( expr_nodes ) <= 1 )
00862 
00863     expr_text = expr_nodes and _coalesceTextNodeChildren( expr_nodes[ 0 ]
00864                                                         , encoding
00865                                                         ) or ''
00866 
00867     return { 'value' : value_text
00868            , 'type' : value_type
00869            , 'expression' : expr_text
00870            }
00871 
00872 _SEMICOLON_LIST_SPLITTER = re.compile( r';[ ]*' )
00873 
00874 def _extractMatchNode( parent, encoding=None ):
00875 
00876     nodes = parent.getElementsByTagName( 'match' )
00877 
00878     result = {}
00879 
00880     for node in nodes:
00881 
00882         name = _getNodeAttribute( node, 'name', encoding )
00883         values = _getNodeAttribute( node, 'values', encoding )
00884         result[ name ] = _SEMICOLON_LIST_SPLITTER.split( values )
00885 
00886     return result
00887 
00888 def _guessVariableType( value ):
00889 
00890     from DateTime.DateTime import DateTime
00891 
00892     if value is None:
00893         return 'none'
00894 
00895     if isinstance( value, DateTime ):
00896         return 'datetime'
00897 
00898     if isinstance( value, bool ):
00899         return 'bool'
00900 
00901     if isinstance( value, int ):
00902         return 'int'
00903 
00904     if isinstance( value, float ):
00905         return 'float'
00906 
00907     if isinstance( value, basestring ):
00908         return 'string'
00909 
00910     return 'unknown'
00911 
00912 def _convertVariableValue( value, type_id ):
00913 
00914     from DateTime.DateTime import DateTime
00915 
00916     if type_id == 'none':
00917         return None
00918 
00919     if type_id == 'datetime':
00920 
00921         return DateTime( value )
00922 
00923     if type_id == 'bool':
00924 
00925         if isinstance( value, basestring ):
00926 
00927             value = str( value ).lower()
00928 
00929             return value in ( 'true', 'yes', '1' )
00930 
00931         else:
00932             return bool( value )
00933 
00934     if type_id == 'int':
00935         return int( value )
00936 
00937     if type_id == 'float':
00938         return float( value )
00939 
00940     return value
00941 
00942 from Products.PythonScripts.PythonScript import PythonScript
00943 from Products.ExternalMethod.ExternalMethod import ExternalMethod
00944 from OFS.DTMLMethod import DTMLMethod
00945 
00946 _METATYPE_SUFFIXES = \
00947 { PythonScript.meta_type : 'py'
00948 , DTMLMethod.meta_type : 'dtml'
00949 }
00950 
00951 def _initDCWorkflow( workflow
00952                    , title
00953                    , description
00954                    , state_variable
00955                    , initial_state
00956                    , states
00957                    , transitions
00958                    , variables
00959                    , worklists
00960                    , permissions
00961                    , scripts
00962                    , context
00963                    ):
00964     """ Initialize a DC Workflow using values parsed from XML.
00965     """
00966     workflow.title = title
00967     workflow.description = description
00968     workflow.state_var = state_variable
00969     workflow.initial_state = initial_state
00970 
00971     permissions = permissions[:]
00972     permissions.sort()
00973     workflow.permissions = tuple(permissions)
00974 
00975     _initDCWorkflowVariables( workflow, variables )
00976     _initDCWorkflowStates( workflow, states )
00977     _initDCWorkflowTransitions( workflow, transitions )
00978     _initDCWorkflowWorklists( workflow, worklists )
00979     _initDCWorkflowScripts( workflow, scripts, context )
00980 
00981 
00982 def _initDCWorkflowVariables( workflow, variables ):
00983 
00984     """ Initialize DCWorkflow variables
00985     """
00986     from Products.DCWorkflow.Variables import VariableDefinition
00987 
00988     for v_info in variables:
00989 
00990         id = str( v_info[ 'variable_id' ] ) # no unicode!
00991         if not workflow.variables.has_key(id):
00992             v = VariableDefinition(id)
00993             workflow.variables._setObject(id, v)
00994         v = workflow.variables._getOb( id )
00995 
00996         guard = v_info[ 'guard' ]
00997         props = { 'guard_roles' : ';'.join( guard[ 'roles' ] )
00998                 , 'guard_permissions' : ';'.join( guard[ 'permissions' ] )
00999                 , 'guard_groups' : ';'.join( guard[ 'groups' ] )
01000                 , 'guard_expr' : guard[ 'expression' ]
01001                 }
01002 
01003         default = v_info[ 'default' ]
01004         default_value = _convertVariableValue( default[ 'value' ]
01005                                              , default[ 'type' ] )
01006 
01007         v.setProperties( description = v_info[ 'description' ]
01008                        , default_value = default_value
01009                        , default_expr = default[ 'expression' ]
01010                        , for_catalog = v_info[ 'for_catalog' ]
01011                        , for_status = v_info[ 'for_status' ]
01012                        , update_always = v_info[ 'update_always' ]
01013                        , props = props
01014                        )
01015 
01016 
01017 def _initDCWorkflowStates( workflow, states ):
01018 
01019     """ Initialize DCWorkflow states
01020     """
01021     from Globals import PersistentMapping
01022     from Products.DCWorkflow.States import StateDefinition
01023 
01024     for s_info in states:
01025 
01026         id = str( s_info[ 'state_id' ] ) # no unicode!
01027         if not workflow.states.has_key(id):
01028             s = StateDefinition(id)
01029             workflow.states._setObject(id, s)
01030         s = workflow.states._getOb( id )
01031 
01032         s.setProperties( title = s_info[ 'title' ]
01033                        , description = s_info[ 'description' ]
01034                        , transitions = s_info[ 'transitions' ]
01035                        )
01036 
01037         for k, v in s_info[ 'permissions' ].items():
01038             s.setPermission( k, isinstance(v, list), v )
01039 
01040         gmap = s.group_roles = PersistentMapping()
01041 
01042         for group_id, roles in s_info[ 'groups' ]:
01043             gmap[ group_id ] = roles
01044 
01045         vmap = s.var_values = PersistentMapping()
01046 
01047         for name, v_info in s_info[ 'variables' ].items():
01048 
01049             value = _convertVariableValue( v_info[ 'value' ]
01050                                          , v_info[ 'type' ] )
01051 
01052             vmap[ name ] = value
01053 
01054 
01055 def _initDCWorkflowTransitions( workflow, transitions ):
01056 
01057     """ Initialize DCWorkflow transitions
01058     """
01059     from Globals import PersistentMapping
01060     from Products.DCWorkflow.Transitions import TransitionDefinition
01061 
01062     for t_info in transitions:
01063 
01064         id = str( t_info[ 'transition_id' ] ) # no unicode!
01065         if not workflow.transitions.has_key(id):
01066             t = TransitionDefinition(id)
01067             workflow.transitions._setObject(id, t)
01068         t = workflow.transitions._getOb( id )
01069 
01070         trigger_type = list( TRIGGER_TYPES ).index( t_info[ 'trigger' ] )
01071 
01072         action = t_info[ 'action' ]
01073 
01074         guard = t_info[ 'guard' ]
01075         props = { 'guard_roles' : ';'.join( guard[ 'roles' ] )
01076                 , 'guard_permissions' : ';'.join( guard[ 'permissions' ] )
01077                 , 'guard_groups' : ';'.join( guard[ 'groups' ] )
01078                 , 'guard_expr' : guard[ 'expression' ]
01079                 }
01080 
01081         t.setProperties( title = t_info[ 'title' ]
01082                        , description = t_info[ 'description' ]
01083                        , new_state_id = t_info[ 'new_state' ]
01084                        , trigger_type = trigger_type
01085                        , script_name = t_info[ 'before_script' ]
01086                        , after_script_name = t_info[ 'after_script' ]
01087                        , actbox_name = action[ 'name' ]
01088                        , actbox_url = action[ 'url' ]
01089                        , actbox_category = action[ 'category' ]
01090                        , props = props
01091                        )
01092         var_mapping = [(name, Expression(text)) for name, text in
01093                        t_info[ 'variables' ].items()]
01094         t.var_exprs = PersistentMapping(var_mapping)
01095 
01096 def _initDCWorkflowWorklists( workflow, worklists ):
01097 
01098     """ Initialize DCWorkflow worklists
01099     """
01100     from Globals import PersistentMapping
01101     from Products.DCWorkflow.Worklists import WorklistDefinition
01102 
01103     for w_info in worklists:
01104 
01105         id = str( w_info[ 'worklist_id' ] ) # no unicode!
01106         if not workflow.worklists.has_key(id):
01107             w = WorklistDefinition(id)
01108             workflow.worklists._setObject(id, w)
01109         w = workflow.worklists._getOb( id )
01110 
01111         action = w_info[ 'action' ]
01112 
01113         guard = w_info[ 'guard' ]
01114         props = { 'guard_roles' : ';'.join( guard[ 'roles' ] )
01115                 , 'guard_permissions' : ';'.join( guard[ 'permissions' ] )
01116                 , 'guard_groups' : ';'.join( guard[ 'groups' ] )
01117                 , 'guard_expr' : guard[ 'expression' ]
01118                 }
01119 
01120         w.setProperties( description = w_info[ 'description' ]
01121                        , actbox_name = action[ 'name' ]
01122                        , actbox_url = action[ 'url' ]
01123                        , actbox_category = action[ 'category' ]
01124                        , props = props
01125                        )
01126 
01127         w.var_matches = PersistentMapping()
01128         for k, v in w_info[ 'match' ].items():
01129             w.var_matches[ str( k ) ] = tuple( [ str(x) for x in v ] )
01130 
01131 def _initDCWorkflowScripts( workflow, scripts, context ):
01132 
01133     """ Initialize DCWorkflow scripts
01134     """
01135     for s_info in scripts:
01136 
01137         id = str( s_info[ 'script_id' ] ) # no unicode!
01138         meta_type = s_info[ 'meta_type' ]
01139         filename = s_info[ 'filename' ]
01140         file = ''
01141 
01142         if filename:
01143             file = context.readDataFile( filename )
01144 
01145         if meta_type == PythonScript.meta_type:
01146             script = PythonScript( id )
01147             script.write( file )
01148 
01149         elif meta_type == ExternalMethod.meta_type:
01150             script = ExternalMethod( id
01151                                    , ''
01152                                    , s_info['module']
01153                                    , s_info['function']
01154                                    )
01155 
01156         elif meta_type == DTMLMethod.meta_type:
01157             script = DTMLMethod( file, __name__=id )
01158 
01159         else:
01160             for mt in workflow.scripts.filtered_meta_types():
01161                 if mt['name']==meta_type:
01162                     if hasattr(mt['instance'], 'write'):
01163                         script = mt['instance'](id)
01164                         script.write(file)
01165                     else:
01166                         script = mt['instance'](file, __name__=id)
01167                     break
01168             else:
01169                 raise ValueError, 'Invalid type: %s' % meta_type
01170 
01171         if workflow.scripts.has_key(id):
01172             workflow.scripts._delObject(id)
01173         workflow.scripts._setObject( id, script )
01174 
01175 #
01176 #   deprecated DOM parsing utilities
01177 #
01178 _marker = object()
01179 
01180 def _queryNodeAttribute( node, attr_name, default, encoding=None ):
01181 
01182     """ Extract a string-valued attribute from node.
01183 
01184     o Return 'default' if the attribute is not present.
01185     """
01186     attr_node = node.attributes.get( attr_name, _marker )
01187 
01188     if attr_node is _marker:
01189         return default
01190 
01191     value = attr_node.nodeValue
01192 
01193     if encoding is not None:
01194         value = value.encode( encoding )
01195 
01196     return value
01197 
01198 def _getNodeAttribute( node, attr_name, encoding=None ):
01199 
01200     """ Extract a string-valued attribute from node.
01201     """
01202     value = _queryNodeAttribute( node, attr_name, _marker, encoding )
01203 
01204     if value is _marker:
01205         raise ValueError, 'Invalid attribute: %s' % attr_name
01206 
01207     return value
01208 
01209 def _queryNodeAttributeBoolean( node, attr_name, default ):
01210 
01211     """ Extract a string-valued attribute from node.
01212 
01213     o Return 'default' if the attribute is not present.
01214     """
01215     attr_node = node.attributes.get( attr_name, _marker )
01216 
01217     if attr_node is _marker:
01218         return default
01219 
01220     value = node.attributes[ attr_name ].nodeValue.lower()
01221 
01222     return value in ( 'true', 'yes', '1' )
01223 
01224 def _getNodeAttributeBoolean( node, attr_name ):
01225 
01226     """ Extract a string-valued attribute from node.
01227     """
01228     value = node.attributes[ attr_name ].nodeValue.lower()
01229 
01230     return value in ( 'true', 'yes', '1' )
01231 
01232 def _coalesceTextNodeChildren( node, encoding=None ):
01233 
01234     """ Concatenate all childe text nodes into a single string.
01235     """
01236     from xml.dom import Node
01237     fragments = []
01238     node.normalize()
01239     child = node.firstChild
01240 
01241     while child is not None:
01242 
01243         if child.nodeType == Node.TEXT_NODE:
01244             fragments.append( child.nodeValue )
01245 
01246         child = child.nextSibling
01247 
01248     joined = ''.join( fragments )
01249 
01250     if encoding is not None:
01251         joined = joined.encode( encoding )
01252 
01253     return ''.join( [ line.lstrip() for line in joined.splitlines(True) ] )
01254 
01255 def _extractDescriptionNode(parent, encoding=None):
01256 
01257     d_nodes = parent.getElementsByTagName('description')
01258     if d_nodes:
01259         return _coalesceTextNodeChildren(d_nodes[0], encoding)
01260     else:
01261         return ''