Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ensure parser knows about all namepsaces #402

Merged
merged 1 commit into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 6 additions & 11 deletions SpiffWorkflow/bpmn/parser/node_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,12 @@ def bpmn_attributes(self):
def get_description(self):
return self.process_parser.parser.spec_descriptions.get(self.node.tag)

def xpath(self, xpath, extra_ns=None):
return self._xpath(self.node, xpath, extra_ns)
def xpath(self, xpath):
return self._xpath(self.node, xpath)

def doc_xpath(self, xpath, extra_ns=None):
def doc_xpath(self, xpath):
root = self.node.getroottree().getroot()
return self._xpath(root, xpath, extra_ns)
return self._xpath(root, xpath)

def attribute(self, attribute, namespace=None, node=None):
if node is None:
Expand Down Expand Up @@ -156,13 +156,8 @@ def _get_lane(self):
if noderef is not None:
return noderef.getparent().get('name')

def _xpath(self, node, xpath, extra_ns=None):
if extra_ns is not None:
nsmap = self.nsmap.copy()
nsmap.update(extra_ns)
else:
nsmap = self.nsmap
return node.xpath(xpath, namespaces=nsmap)
def _xpath(self, node, xpath):
return node.xpath(xpath, namespaces=self.nsmap)

def raise_validation_exception(self, message):
raise ValidationException(message, self.node, self.filename)
11 changes: 7 additions & 4 deletions SpiffWorkflow/dmn/parser/BpmnDmnParser.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from ...bpmn.parser.ValidationException import ValidationException

from ...bpmn.parser.BpmnParser import BpmnParser, BpmnValidator
from ...dmn.parser.DMNParser import DMNParser, get_dmn_ns
from ...dmn.parser.DMNParser import DMNParser
from ..engine.DMNEngine import DMNEngine

XSD_DIR = os.path.join(os.path.dirname(__file__), 'schema')
Expand Down Expand Up @@ -62,16 +62,19 @@ def add_dmn_xml(self, node, filename=None):
"""
Add the given lxml representation of the DMN file to the parser's set.
"""
nsmap = get_dmn_ns(node)
namespaces = self.namespaces.copy()
namespaces.update(node.nsmap)
if None in namespaces:
namespaces['dmn'] = namespaces.pop(None)
# We have to create a dmn validator on the fly, because we support multiple versions
# If we have a bpmn validator, assume DMN validation should be done as well.
# I don't like this, but I don't see a better solution.
schema = self.dmn_schemas.get(nsmap.get('dmn'))
schema = self.dmn_schemas.get(namespaces.get('dmn'))
if self.validator and schema is not None:
validator = BpmnValidator(schema)
validator.validate(node, filename)

dmn_parser = DMNParser(self, node, nsmap, filename=filename)
dmn_parser = DMNParser(self, node, namespaces, filename=filename)
self.dmn_parsers[dmn_parser.bpmn_id] = dmn_parser
self.dmn_parsers_by_name[dmn_parser.get_name()] = dmn_parser

Expand Down
29 changes: 7 additions & 22 deletions SpiffWorkflow/dmn/parser/DMNParser.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

import ast

from SpiffWorkflow.bpmn.parser.node_parser import NodeParser, DEFAULT_NSMAP
from SpiffWorkflow.bpmn.parser.node_parser import NodeParser
from SpiffWorkflow.bpmn.parser.ValidationException import ValidationException

from SpiffWorkflow.bpmn.parser.util import xpath_eval
Expand All @@ -34,20 +34,6 @@
Rule,
)

def get_dmn_ns(node):
"""
Returns the namespace definition for the current DMN

:param node: the XML node for the DMN document
"""
nsmap = DEFAULT_NSMAP.copy()
if 'http://www.omg.org/spec/DMN/20151101/dmn.xsd' in node.nsmap.values():
nsmap['dmn'] = 'http://www.omg.org/spec/DMN/20151101/dmn.xsd'
elif 'http://www.omg.org/spec/DMN/20180521/DI/' in node.nsmap.values():
nsmap['dmn'] = 'http://www.omg.org/spec/DMN/20180521/DI/'
elif 'https://www.omg.org/spec/DMN/20191111/MODEL/' in node.nsmap.values():
nsmap['dmn'] = 'https://www.omg.org/spec/DMN/20191111/MODEL/'
return nsmap

class DMNParser(NodeParser):
"""
Expand Down Expand Up @@ -79,20 +65,20 @@ def __init__(self, p, node, nsmap, svg=None, filename=None):
self.filename = filename

def parse(self):
self.decision = self._parse_decision(self.node.findall('{*}decision'))
self.decision = self._parse_decision(self.xpath('.//dmn:decision'))

@property
def bpmn_id(self):
"""
Returns the process ID
"""
return self.node.findall('{*}decision[1]')[0].get('id')
return self.xpath('dmn:decision[1]')[0].get('id')

def get_name(self):
"""
Returns the process name (or ID, if no name is included in the file)
"""
return self.node.findall('{*}decision[1]')[0].get('name')
return self.xpath('dmn:decision[1]')[0].get('name')

def _parse_decision(self, root):
decision_elements = list(root)
Expand All @@ -115,7 +101,7 @@ def _parse_decision(self, root):
return decision

def _parse_decision_tables(self, decision, decisionElement):
for decision_table_element in decisionElement.findall('{*}decisionTable'):
for decision_table_element in decisionElement.findall('dmn:decisionTable', namespaces=self.nsmap):
name = decision_table_element.attrib.get('name', '')
hitPolicy = decision_table_element.attrib.get('hitPolicy', 'UNIQUE').upper()
decision_table = DecisionTable(decision_table_element.attrib['id'],
Expand Down Expand Up @@ -146,12 +132,11 @@ def _parse_inputs_outputs(self, decisionTable,

def _parse_input(self, input_element):
type_ref = None
prefix = self.nsmap['dmn']
xpath = xpath_eval(input_element, {'dmn': prefix})
xpath = xpath_eval(input_element, self.nsmap)
expression = None
for input_expression in xpath('dmn:inputExpression'):
type_ref = input_expression.attrib.get('typeRef', '')
expression_node = input_expression.find('{' + prefix + '}text')
expression_node = input_expression.find('dmn:text', namespaces=self.nsmap)
if expression_node is not None:
expression = expression_node.text

Expand Down
10 changes: 8 additions & 2 deletions tests/SpiffWorkflow/dmn/DecisionRunner.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
from lxml import etree

from SpiffWorkflow.dmn.engine.DMNEngine import DMNEngine
from SpiffWorkflow.dmn.parser.DMNParser import DMNParser, get_dmn_ns
from SpiffWorkflow.dmn.parser.DMNParser import DMNParser
from SpiffWorkflow.bpmn.parser.node_parser import DEFAULT_NSMAP

class WorkflowSpec:
def __init__(self):
Expand Down Expand Up @@ -38,7 +39,12 @@ def __init__(self, script_engine, filename, path=''):
with open(fn) as fh:
node = etree.parse(fh)

self.dmnParser = DMNParser(None, node.getroot(), get_dmn_ns(node.getroot()))
nsmap = DEFAULT_NSMAP.copy()
nsmap.update(node.getroot().nsmap)
if None in nsmap:
nsmap['dmn'] = nsmap.pop(None)

self.dmnParser = DMNParser(None, node.getroot(), nsmap)
self.dmnParser.parse()

decision = self.dmnParser.decision
Expand Down
1 change: 1 addition & 0 deletions tests/SpiffWorkflow/dmn/VersionTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class DmnVersionTest(unittest.TestCase):

def setUp(self):
self.parser = BpmnDmnParser()
self.parser.namespaces.update({'dmn': 'https://www.omg.org/spec/DMN/20191111/MODEL/'})

def test_load_v1_0(self):
filename = os.path.join(data_dir, 'dmn_version_20151101_test.dmn')
Expand Down
Loading