Skip to content

Commit

Permalink
Merge pull request #242 from gaphor/reduce-parser-complexity
Browse files Browse the repository at this point in the history
Reduce parser complexity
  • Loading branch information
amolenaar committed Nov 25, 2019
2 parents 59ad542 + 715020a commit aa5c8a3
Showing 1 changed file with 44 additions and 19 deletions.
63 changes: 44 additions & 19 deletions gaphor/storage/parser.py
Expand Up @@ -131,87 +131,109 @@ def __init__(self):
def push(self, element, state):
"""Add an element to the item stack.
"""
self.__stack.append((element, state))
self._stack.append((element, state))

def pop(self):
"""Return the last item on the stack. The item is removed from
the stack.
"""
return self.__stack.pop()[0]
return self._stack.pop()[0]

def peek(self, depth=1):
"""Return the last item on the stack. The item is not removed.
"""
return self.__stack[-1 * depth][0]
return self._stack[-1 * depth][0]

def state(self):
"""Return the current state of the parser.
"""
try:
return self.__stack[-1][1]
return self._stack[-1][1]
except IndexError:
return ROOT

def endDTD(self):
pass

def startDocument(self):
"""Start of document: all our attributes are initialized.
"""
self.version = None
self.gaphor_version = None
self.elements: Dict[str, Union[element, canvasitem]] = OrderedDict()
self.__stack: List[Tuple[Union[element, canvas, canvasitem], State]] = []
self._stack: List[Tuple[Union[element, canvas, canvasitem], State]] = []
self.text = ""
self._start_element_handlers = (
self.start_element,
self.start_canvas,
self.start_canvas_item,
self.start_attribute,
self.start_reference,
self.start_attribute_value,
self.start_root,
self.invalid_tag,
)

def endDocument(self):
if len(self.__stack) != 0:
if len(self._stack) != 0:
raise ParserException("Invalid XML document.")

def startElement(self, name, attrs):
self.text = ""

state = self.state()

for h in self._start_element_handlers:
if h(state, name, attrs):
break

def start_element(self, state, name, attrs):
# Read a element class. The name of the tag is the class name:
if state == GAPHOR:
id = attrs["id"]
e = element(id, name)
assert id not in self.elements.keys(), f"{id} already defined"
self.elements[id] = e
self.push(e, name == "Diagram" and DIAGRAM or ELEMENT)
return True

def start_canvas(self, state, name, attrs):
# Special treatment for the <canvas> tag in a Diagram:
elif state == DIAGRAM and name == "canvas":
if state == DIAGRAM and name == "canvas":
c = canvas()
self.peek().canvas = c
self.push(c, CANVAS)
return True

def start_canvas_item(self, state, name, attrs):
# Items in a canvas are referenced by the <item> tag:
elif state in (CANVAS, ITEM) and name == "item":
if state in (CANVAS, ITEM) and name == "item":
id = attrs["id"]
ci = canvasitem(id, attrs["type"])
assert id not in self.elements.keys(), f"{id} already defined"
self.elements[id] = ci
self.peek().canvasitems.append(ci)
self.push(ci, ITEM)
return True

def start_attribute(self, state, name, attrs):
# Store the attribute name on the stack, so we can use it later
# to store the <ref>, <reflist> or <val> content:
elif state in (ELEMENT, DIAGRAM, CANVAS, ITEM):
if state in (ELEMENT, DIAGRAM, CANVAS, ITEM):
# handle 'normal' attributes
self.push(name, ATTR)
return True

def start_reference(self, state, name, attrs):
# Reference list:
elif state == ATTR and name == "reflist":
if state == ATTR and name == "reflist":
self.push(self.peek(), REFLIST)
return True

# Reference with multiplicity 1:
elif state == ATTR and name == "ref":
n = self.peek(1)
# Fetch the element instance from the stack
r = self.peek(2).references[n] = attrs["refid"]
self.push(None, REF)
return True

# Reference with multiplicity *:
elif state == REFLIST and name == "ref":
Expand All @@ -224,24 +246,27 @@ def startElement(self, name, attrs):
except KeyError:
r[n] = [refid]
self.push(None, REF)
return True

def start_attribute_value(self, state, name, attrs):
# We need to get the text within the <val> tag:
elif state == ATTR and name == "val":
if state == ATTR and name == "val":
self.push(None, VAL)
return True

def start_root(self, state, name, attrs):
# The <gaphor> tag is the toplevel tag:
elif state == ROOT and name == "gaphor":
if state == ROOT and name == "gaphor":
assert attrs["version"] in ("3.0",)
self.version = attrs["version"]
self.gaphor_version = attrs.get("gaphor-version")
if not self.gaphor_version:
self.gaphor_version = attrs.get("gaphor_version")
self.push(None, GAPHOR)
return True

else:
raise ParserException(
f"Invalid XML: tag <{name}> not known (state = {state})"
)
def invalid_tag(self, state, name, attrs):
raise ParserException(f"Invalid XML: tag <{name}> not known (state = {state})")

def endElement(self, name):
# Put the text on the value
Expand Down

0 comments on commit aa5c8a3

Please sign in to comment.