Hi there,

xpdl-support in zope.wfmc is outdated if one wants to use current
tools for writing workflow definitions like together workflow editor
(twe). twe now uses xpdl v2.1 (2.2 was released in spring), but the
package zope.wfmc only supports v1.0.

I improved the package so that basic elements of xpdl2.1/wfmc work
under zope because I use wfmc-workflows a lot in my bluebream app.

It's not only the namespace, that changed, but some terms too.
AND-splits are now Parallel-splits, Lanes were introduced and
TaskApplications are used instead of Tools.

A patch and the file xpdl.py is attached. Maybe it will help someone.

I didn't write tests yet, because I assert that it works implicitly by
writing tests for my app :)

Kind regards
Christian

BTW: Don't know if this is the right list - zope.wfmc is not part of
ztk/bluebream but this list seems to be now what old zope3-users was..

Attachment: patch
Description: Binary data

##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""XPDL reader for process definitions

$Id: xpdl.py 70052 2006-09-08 12:27:59Z adamg $
"""

import sys
import xml.sax
import xml.sax.xmlreader
import xml.sax.handler

import zope.wfmc.process

xpdl10ns = "http://www.wfmc.org/2002/XPDL1.0";
xpdl21ns = "http://www.wfmc.org/2008/XPDL2.1";


class HandlerError(Exception):

    def __init__(self, orig, tag, locator):
        self.orig = orig
        self.tag = tag
        self.xml = locator.getSystemId()
        self.line = locator.getLineNumber()

    def __repr__(self):
        return ('%r\nFile "%s", line %s. in %s'
                % (self.orig, self.xml, self.line, self.tag))

    def __str__(self):
        return ('%s\nFile "%s", line %s. in %s'
                % (self.orig, self.xml, self.line, self.tag))


class Package(dict):

    def __init__(self):
        self.applications = {}
        self.participants = {}
    
    def defineApplications(self, **applications):
        for id, application in applications.items():
            application.id = id
            self.applications[id] = application

    def defineParticipants(self, **participants):
        for id, participant in participants.items():
            participant.id = id
            self.participants[id] = participant


class XPDLHandler(xml.sax.handler.ContentHandler):

    start_handlers = {}
    end_handlers = {}
    text = u''
    
    ProcessDefinitionFactory = zope.wfmc.process.ProcessDefinition
    ParticipantFactory = zope.wfmc.process.Participant
    ApplicationFactory = zope.wfmc.process.Application
    ActivityDefinitionFactory = zope.wfmc.process.ActivityDefinition
    TransitionDefinitionFactory = zope.wfmc.process.TransitionDefinition

    def __init__(self, package):
        self.package = package
        self.stack = []

    def startElementNS(self, name, qname, attrs):
        handler = self.start_handlers.get(name)
        if handler:
            try:
                result = handler(self, attrs)
            except:
                raise HandlerError(sys.exc_info()[1], name[1], self.locator
                    ), None, sys.exc_info()[2]
        else:
            result = None

        if result is None:
            # Just dup the top of the stack
            result = self.stack[-1]
            
        self.stack.append(result)
        self.text = u''

    def endElementNS(self, name, qname):
        last = self.stack.pop()
        handler = self.end_handlers.get(name)
        if handler:
            try:
                handler(self, last)
            except:
                raise HandlerError(sys.exc_info()[1], name[1], self.locator
                    ), None, sys.exc_info()[2]

        self.text = u''

    def characters(self, text):
        self.text += text

    def setDocumentLocator(self, locator):
        self.locator = locator

    ######################################################################
    # Application handlers

    # Pointless container elements that we want to "ignore" by having them
    # dup their containers:
    def Package(self, attrs):
        package = self.package
        package.id = attrs[(None, 'Id')]
        package.__name__ = attrs.get((None, 'Name'))
        return package
    start_handlers[(xpdl10ns, 'Package')] = Package
    start_handlers[(xpdl21ns, 'Package')] = Package

    def WorkflowProcess(self, attrs):
        id = attrs[(None, 'Id')]
        process = self.ProcessDefinitionFactory(id)
        process.__name__ = attrs.get((None, 'Name'))

        # Copy package data:
        process.defineApplications(**self.package.applications)
        process.defineParticipants(**self.package.participants)

        self.package[id] = process
        return process
    start_handlers[(xpdl10ns, 'WorkflowProcess')] = WorkflowProcess
    start_handlers[(xpdl21ns, 'WorkflowProcess')] = WorkflowProcess

    paramter_types = {
        'IN': zope.wfmc.process.InputParameter,
        'OUT': zope.wfmc.process.OutputParameter,
        'INOUT': zope.wfmc.process.InputOutputParameter,
        }
        
    
    def FormalParameter(self, attrs):
        mode = attrs.get((None, 'Mode'), 'IN')
        id = attrs[(None, 'Id')]
        self.stack[-1].defineParameters(*[self.paramter_types[mode](id)])
    start_handlers[(xpdl10ns, 'FormalParameter')] = FormalParameter
    start_handlers[(xpdl21ns, 'FormalParameter')] = FormalParameter
    
    def Participant(self, attrs):
        id = attrs[(None, 'Id')]
        name = attrs.get((None, 'Name'))
        participant = self.ParticipantFactory(name)
        self.stack[-1].defineParticipants(**{str(id): participant})
        return participant
    start_handlers[(xpdl10ns, 'Participant')] = Participant
    start_handlers[(xpdl21ns, 'Participant')] = Participant

    def Application(self, attrs):
        id = attrs[(None, 'Id')]
        name = attrs.get((None, 'Name'))
        app = self.ApplicationFactory()
        app.id = id
        if name:
            app.__name__ = name
        return app
    start_handlers[(xpdl10ns, 'Application')] = Application
    start_handlers[(xpdl21ns, 'Application')] = Application
    
    def application(self, app):
        self.stack[-1].defineApplications(**{str(app.id): app})
    end_handlers[(xpdl10ns, 'Application')] = application
    end_handlers[(xpdl21ns, 'Application')] = application

    def description(self, ignored):
        if self.stack[-1] is not None:
            self.stack[-1].description = self.text
    end_handlers[(xpdl10ns, 'Description')] = description
    end_handlers[(xpdl21ns, 'Description')] = description

    ######################################################################
    # Activity definitions

    def ActivitySet(self, attrs):
        raise NotImplementedError("ActivitySet")
    end_handlers[(xpdl10ns, 'ActivitySet')] = ActivitySet
    end_handlers[(xpdl21ns, 'ActivitySet')] = ActivitySet

    def Activity(self, attrs):
        id = attrs[(None, 'Id')]
        name = attrs.get((None, 'Name'))
        activity = self.ActivityDefinitionFactory(name)
        activity.id = id
        self.stack[-1].defineActivities(**{str(id): activity})
        return activity
    start_handlers[(xpdl10ns, 'Activity')] = Activity
    start_handlers[(xpdl21ns, 'Activity')] = Activity

    def Tool(self, attrs):
        """ Tools were removed from the spec in XPDL 2.1"""
        return Tool(attrs[(None, 'Id')])
    start_handlers[(xpdl10ns, 'Tool')] = Tool

    def tool(self, tool):
        self.stack[-1].addApplication(tool.id, tool.parameters)
    end_handlers[(xpdl10ns, 'Tool')] = tool

    def TaskApplication(self, attrs):
        """ In XPDL 2.1 Task/TaskApplication is used instead of Tools."""
        return TaskApplication(attrs[(None, 'Id')])
    start_handlers[(xpdl21ns, 'TaskApplication')] = TaskApplication
    
    def taskapplication(self, taskapplication):
        self.stack[-1].addApplication(taskapplication.id, taskapplication.parameters)
    end_handlers[(xpdl21ns, 'TaskApplication')] = taskapplication

    def actualparameter(self, ignored):
        self.stack[-1].parameters += (self.text, )
    end_handlers[(xpdl10ns, 'ActualParameter')] = actualparameter
    end_handlers[(xpdl21ns, 'ActualParameter')] = actualparameter

    def performer(self, ignored):
        # A performer may be defined in several contexts (lanes,
        # activities etc.) but we only use it in activities.
        if isinstance(self.stack[-1], self.ActivityDefinitionFactory):
            self.stack[-1].definePerformer(self.text.strip())
    end_handlers[(xpdl10ns, 'Performer')] = performer
    end_handlers[(xpdl21ns, 'Performer')] = performer

    def Join(self, attrs):
        Type = attrs.get((None, 'Type'))
        if Type == u'AND':
            self.stack[-1].andJoin(True)
    start_handlers[(xpdl10ns, 'Join')] = Join
    start_handlers[(xpdl21ns, 'Join')] = Join

    def Split(self, attrs):
        # In 2.1 'Parallel' is used for 'AND'
        Type = attrs.get((None, 'Type'))
        if Type == u'AND' or Type == u'Parallel':
            self.stack[-1].andSplit(True)
    start_handlers[(xpdl10ns, 'Split')] = Split
    start_handlers[(xpdl21ns, 'Split')] = Split

    def TransitionRef(self, attrs):
        Id = attrs.get((None, 'Id'))
        self.stack[-1].addOutgoing(Id)
    start_handlers[(xpdl10ns, 'TransitionRef')] = TransitionRef
    start_handlers[(xpdl21ns, 'TransitionRef')] = TransitionRef
        

    # Activity definitions
    ######################################################################

    def Transition(self, attrs):
        id = attrs[(None, 'Id')]
        name = attrs.get((None, 'Name'))
        from_ = attrs.get((None, 'From'))
        to = attrs.get((None, 'To'))
        transition = self.TransitionDefinitionFactory(from_, to)
        transition.id = id
        transition.__name__ = name
        return transition
    start_handlers[(xpdl10ns, 'Transition')] = Transition
    start_handlers[(xpdl21ns, 'Transition')] = Transition
    
    def transition(self, transition):
        self.stack[-1].defineTransitions(transition)
    end_handlers[(xpdl10ns, 'Transition')] = transition
    end_handlers[(xpdl21ns, 'Transition')] = transition
    
    def condition(self, ignored):
        assert isinstance(self.stack[-1],
                          self.TransitionDefinitionFactory)

        text = self.text
        self.stack[-1].condition = TextCondition("(%s)" % text)
    end_handlers[(xpdl10ns, 'Condition')] = condition
    end_handlers[(xpdl21ns, 'Condition')] = condition


class Tool:

    def __init__(self, id):
        self.id = id

    parameters = ()

class TaskApplication:
    
    def __init__(self, id):
        self.id = id

    parameters = ()
  
class TextCondition:

    def __init__(self, source):
        self.source = source

        # make sure that we can compile the source
        compile(source, '<string>', 'eval')

    def __getstate__(self):
        return {'source': self.source}

    def __call__(self, data):
        # We *depend* on being able to use the data's dict.
        # TODO This needs to be part of the contract.
        try:
            compiled = self._v_compiled
        except AttributeError:
            self._v_compiled = compile(self.source, '<string>', 'eval')
            compiled = self._v_compiled

        return eval(compiled, {'__builtins__': None}, data.__dict__)
    

def read(file):
    src = xml.sax.xmlreader.InputSource(getattr(file, 'name', '<string>'))
    src.setByteStream(file)
    parser = xml.sax.make_parser()
    package = Package()
    parser.setContentHandler(XPDLHandler(package))
    parser.setFeature(xml.sax.handler.feature_namespaces, True)
    parser.parse(src)
    return package
    
_______________________________________________
bluebream mailing list
[email protected]
https://mail.zope.org/mailman/listinfo/bluebream

Reply via email to