############################################################################## # # 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, '', '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, '', 'eval') compiled = self._v_compiled return eval(compiled, {'__builtins__': None}, data.__dict__) def read(file): src = xml.sax.xmlreader.InputSource(getattr(file, 'name', '')) 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