示例#1
0
    def _fix_call_activities_signavio(self, bpmn, filename):
        """
        Signavio produces slightly invalid BPMN for call activity nodes... It is supposed to put a reference to the id of the called process
        in to the calledElement attribute. Instead it stores a string (which is the name of the process - not its ID, in our interpretation)
        in an extension tag.

        This code gets the name of the 'subprocess reference', finds a process with a matching name, and sets the calledElement attribute
        to the id of the process.

        """
        for node in xpath_eval(bpmn)(".//bpmn:callActivity"):
            calledElement = node.get('calledElement', None)
            if not calledElement:
                signavioMetaData = xpath_eval(node, extra_ns={'signavio':SIGNAVIO_NS})('.//signavio:signavioMetaData[@metaKey="entry"]')
                if not signavioMetaData:
                    raise ValidationException('No Signavio "Subprocess reference" specified.', node=node, filename=filename)
                subprocess_reference = one(signavioMetaData).get('metaValue')
                matches = []
                for b in self.bpmn.itervalues():
                    for p in xpath_eval(b)(".//bpmn:process"):
                        if p.get('name', p.get('id', None)) == subprocess_reference:
                            matches.append(p)
                if not matches:
                    raise ValidationException("No matching process definition found for '%s'." % subprocess_reference, node=node, filename=filename)
                if len(matches) != 1:
                    raise ValidationException("More than one matching process definition found for '%s'." % subprocess_reference, node=node, filename=filename)

                node.set('calledElement', matches[0].get('id'))
示例#2
0
    def add_bpmn_xml(self, bpmn, svg=None, filename=None):
        """
        Add the given lxml representation of the BPMN file to the parser's set.

        :param svg: Optionally, provide the text data for the SVG of the BPMN file
        :param filename: Optionally, provide the source filename.
        """
        xpath = xpath_eval(bpmn)

        processes = xpath('.//bpmn:process') + xpath('.//bpmn:subProcess')
        for process in processes:
            process_parser = self.PROCESS_PARSER_CLASS(self,
                                                       process,
                                                       svg,
                                                       filename=filename,
                                                       doc_xpath=xpath)
            if process_parser.get_id() in self.process_parsers:
                raise ValidationException('Duplicate process ID',
                                          node=process,
                                          filename=filename)
            if process_parser.get_name() in self.process_parsers_by_name:
                raise ValidationException('Duplicate process name',
                                          node=process,
                                          filename=filename)
            self.process_parsers[process_parser.get_id()] = process_parser
            self.process_parsers_by_name[
                process_parser.get_name()] = process_parser
示例#3
0
 def _parse(self):
     start_node_list = self.xpath('.//bpmn:startEvent')
     if not start_node_list:
         raise ValidationException("No start event found", node=self.node, filename=self.filename)
     elif len(start_node_list) != 1:
         raise ValidationException("Only one Start Event is supported in each process", node=self.node, filename=self.filename)
     self.parsing_started = True
     self.parse_node(start_node_list[0])
     self.is_parsed = True
示例#4
0
 def get_subprocess_parser(self):
     called_element = self.node.get('calledElement', None)
     if not called_element:
         raise ValidationException(
             'No "calledElement" attribute for Call Activity.',
             node=self.node,
             filename=self.process_parser.filename)
     return self.parser.get_process_parser(called_element)
示例#5
0
 def connect_outgoing(self, outgoing_task, outgoing_task_node, sequence_flow_node, is_default):
     if is_default:
         super(ExclusiveGatewayParser, self).connect_outgoing(outgoing_task, outgoing_task_node, sequence_flow_node, is_default)
     else:
         cond = self.parser._parse_condition(outgoing_task, outgoing_task_node, sequence_flow_node, task_parser=self)
         if cond is None:
             raise ValidationException('Non-default exclusive outgoing sequence flow without condition', sequence_flow_node, self.process_parser.filename)
         self.task.connect_outgoing_if(cond, outgoing_task, sequence_flow_node.get('id'), sequence_flow_node.get('name', None), self.parser._parse_documentation(sequence_flow_node, task_parser=self))
示例#6
0
 def _check_for_disconnected_boundary_events_signavio(self, bpmn, filename):
     #signavio sometimes disconnects a BoundaryEvent from it's owning task
     #They then show up as intermediateCatchEvents without any incoming sequence flows
     xpath = xpath_eval(bpmn)
     for catch_event in xpath('.//bpmn:intermediateCatchEvent'):
         incoming = xpath('.//bpmn:sequenceFlow[@targetRef="%s"]' % catch_event.get('id'))
         if not incoming:
             raise ValidationException('Intermediate Catch Event has no incoming sequences. This might be a Boundary Event that has been disconnected.',
             node=catch_event, filename=filename)
示例#7
0
    def get_process_id(et_root: etree.Element):
        process_elements = []
        for child in et_root:
            if child.tag.endswith('process') and child.attrib.get('isExecutable', False):
                process_elements.append(child)

        if len(process_elements) == 0:
            raise ValidationException('No executable process tag found')

        # There are multiple root elements
        if len(process_elements) > 1:

            # Look for the element that has the startEvent in it
            for e in process_elements:
                this_element: etree.Element = e
                for child_element in list(this_element):
                    if child_element.tag.endswith('startEvent'):
                        return this_element.attrib['id']

            raise ValidationException('No start event found in %s' % et_root.attrib['id'])

        return process_elements[0].attrib['id']
示例#8
0
    def parse_node(self,node):
        """
        Parses the specified child task node, and returns the task spec.
        This can be called by a TaskParser instance, that is owned by this ProcessParser.
        """

        if node.get('id') in self.parsed_nodes:
            return self.parsed_nodes[node.get('id')]

        (node_parser, spec_class) = self.parser._get_parser_class(node.tag)
        if not node_parser or not spec_class:
            raise ValidationException("There is no support implemented for this task type.", node=node, filename=self.filename)
        np = node_parser(self, spec_class, node)
        task_spec = np.parse_node()

        return task_spec
示例#9
0
    def parse_node(self):
        """
        Parse this node, and all children, returning the connected task spec.
        """

        try:
            self.task = self.create_task()

            self.task.documentation = self.parser._parse_documentation(
                self.node, xpath=self.xpath, task_parser=self)

            boundary_event_nodes = self.process_xpath(
                './/bpmn:boundaryEvent[@attachedToRef="%s"]' % self.get_id())
            if boundary_event_nodes:
                parent_task = _BoundaryEventParent(self.spec,
                                                   '%s.BoundaryEventParent' %
                                                   self.get_id(),
                                                   self.task,
                                                   lane=self.task.lane)
                self.process_parser.parsed_nodes[self.node.get(
                    'id')] = parent_task

                parent_task.connect_outgoing(
                    self.task, '%s.FromBoundaryEventParent' % self.get_id(),
                    None, None)
                for boundary_event in boundary_event_nodes:
                    b = self.process_parser.parse_node(boundary_event)
                    parent_task.connect_outgoing(
                        b, '%s.FromBoundaryEventParent' %
                        boundary_event.get('id'), None, None)
            else:
                self.process_parser.parsed_nodes[self.node.get(
                    'id')] = self.task

            children = []
            outgoing = self.process_xpath(
                './/bpmn:sequenceFlow[@sourceRef="%s"]' % self.get_id())
            if len(outgoing) > 1 and not self.handles_multiple_outgoing():
                raise ValidationException(
                    'Multiple outgoing flows are not supported for tasks of type',
                    node=self.node,
                    filename=self.process_parser.filename)
            for sequence_flow in outgoing:
                target_ref = sequence_flow.get('targetRef')
                target_node = one(
                    self.process_xpath('.//*[@id="%s"]' % target_ref))
                c = self.process_parser.parse_node(target_node)
                children.append((c, target_node, sequence_flow))

            if children:
                default_outgoing = self.node.get('default')
                if not default_outgoing:
                    (c, target_node, sequence_flow) = children[0]
                    default_outgoing = sequence_flow.get('id')

                for (c, target_node, sequence_flow) in children:
                    self.connect_outgoing(
                        c, target_node, sequence_flow,
                        sequence_flow.get('id') == default_outgoing)

            return parent_task if boundary_event_nodes else self.task
        except ValidationException, vx:
            raise
示例#10
0
class TaskParser(object):
    """
    This class parses a single BPMN task node, and returns the Task Spec for that node.

    It also results in the recursive parsing of connected tasks, connecting all
    outgoing transitions, once the child tasks have all been parsed.
    """
    def __init__(self, process_parser, spec_class, node):
        """
        Constructor.

        :param process_parser: the owning process parser instance
        :param spec_class: the type of spec that should be created. This allows a subclass of BpmnParser to
        provide a specialised spec class, without extending the TaskParser.
        :param node: the XML node for this task
        """
        self.parser = process_parser.parser
        self.process_parser = process_parser
        self.spec_class = spec_class
        self.process_xpath = self.process_parser.xpath
        self.spec = self.process_parser.spec
        self.node = node
        self.xpath = xpath_eval(node)

    def parse_node(self):
        """
        Parse this node, and all children, returning the connected task spec.
        """

        try:
            self.task = self.create_task()

            self.task.documentation = self.parser._parse_documentation(
                self.node, xpath=self.xpath, task_parser=self)

            boundary_event_nodes = self.process_xpath(
                './/bpmn:boundaryEvent[@attachedToRef="%s"]' % self.get_id())
            if boundary_event_nodes:
                parent_task = _BoundaryEventParent(self.spec,
                                                   '%s.BoundaryEventParent' %
                                                   self.get_id(),
                                                   self.task,
                                                   lane=self.task.lane)
                self.process_parser.parsed_nodes[self.node.get(
                    'id')] = parent_task

                parent_task.connect_outgoing(
                    self.task, '%s.FromBoundaryEventParent' % self.get_id(),
                    None, None)
                for boundary_event in boundary_event_nodes:
                    b = self.process_parser.parse_node(boundary_event)
                    parent_task.connect_outgoing(
                        b, '%s.FromBoundaryEventParent' %
                        boundary_event.get('id'), None, None)
            else:
                self.process_parser.parsed_nodes[self.node.get(
                    'id')] = self.task

            children = []
            outgoing = self.process_xpath(
                './/bpmn:sequenceFlow[@sourceRef="%s"]' % self.get_id())
            if len(outgoing) > 1 and not self.handles_multiple_outgoing():
                raise ValidationException(
                    'Multiple outgoing flows are not supported for tasks of type',
                    node=self.node,
                    filename=self.process_parser.filename)
            for sequence_flow in outgoing:
                target_ref = sequence_flow.get('targetRef')
                target_node = one(
                    self.process_xpath('.//*[@id="%s"]' % target_ref))
                c = self.process_parser.parse_node(target_node)
                children.append((c, target_node, sequence_flow))

            if children:
                default_outgoing = self.node.get('default')
                if not default_outgoing:
                    (c, target_node, sequence_flow) = children[0]
                    default_outgoing = sequence_flow.get('id')

                for (c, target_node, sequence_flow) in children:
                    self.connect_outgoing(
                        c, target_node, sequence_flow,
                        sequence_flow.get('id') == default_outgoing)

            return parent_task if boundary_event_nodes else self.task
        except ValidationException, vx:
            raise
        except Exception, ex:
            exc_info = sys.exc_info()
            tb = "".join(
                traceback.format_exception(exc_info[0], exc_info[1],
                                           exc_info[2]))
            LOG.error("%r\n%s", ex, tb)
            raise ValidationException("%r" % (ex),
                                      node=self.node,
                                      filename=self.process_parser.filename)