From acb8a7a793a4b9f122ed5a8d4f4a976ab009f561 Mon Sep 17 00:00:00 2001 From: James Turner Date: Fri, 10 Feb 2017 11:55:30 +0000 Subject: [PATCH] Fix catalog parsing, add unit-tests Fixes issues with conflated nodes and skipped previews in the catalog XML. --- catalog/sgprops.py | 40 ++++++++++++++++---- catalog/testData/bad-index.xml | 11 ++++++ catalog/testData/props1.xml | 25 +++++++++++++ catalog/testData/props2.xml | 20 ++++++++++ catalog/testData/root-include.xml | 22 +++++++++++ catalog/testData/sub-include.xml | 6 +++ catalog/test_sgprops.py | 61 +++++++++++++++++++++++++++++++ catalog/update-catalog.py | 3 +- 8 files changed, 179 insertions(+), 9 deletions(-) create mode 100644 catalog/testData/bad-index.xml create mode 100644 catalog/testData/props1.xml create mode 100644 catalog/testData/props2.xml create mode 100644 catalog/testData/root-include.xml create mode 100644 catalog/testData/sub-include.xml create mode 100755 catalog/test_sgprops.py diff --git a/catalog/sgprops.py b/catalog/sgprops.py index f523657..9901154 100644 --- a/catalog/sgprops.py +++ b/catalog/sgprops.py @@ -144,6 +144,22 @@ class Node(object): return n; +class ParseState: + def __init__(self): + self._counters = {} + + def getNextIndex(self, name): + if name in self._counters: + self._counters[name] += 1 + else: + self._counters[name] = 0 + return self._counters[name] + + def recordExplicitIndex(self, name, index): + if not name in self._counters: + self._counters[name] = index + else: + self._counters[name] = max(self._counters[name], index) class PropsHandler(handler.ContentHandler): def __init__(self, root = None, path = None, includePaths = []): @@ -152,6 +168,7 @@ class PropsHandler(handler.ContentHandler): self._basePath = os.path.dirname(path) self._includes = includePaths self._locator = None + self._stateStack = [ParseState()] if root is None: # make a nameless root node @@ -163,25 +180,31 @@ class PropsHandler(handler.ContentHandler): def startElement(self, name, attrs): self._content = None - if 'include' in attrs.keys(): - self.handleInclude(attrs['include']) - if (name == 'PropertyList'): + # still need to handle includes on the root element + if 'include' in attrs.keys(): + self.handleInclude(attrs['include']) return - + + currentState = self._stateStack[-1] if 'n' in attrs.keys(): try: index = int(attrs['n']) except: - print "Invalid index at line:", self._locator.getLineNumber(), "of", self._path - self._current = self._current.addChild(name) - return + raise IndexError("Invalid index at line:", self._locator.getLineNumber(), "of", self._path) + currentState.recordExplicitIndex(name, index) self._current = self._current.getChild(name, index, create=True) else: + index = currentState.getNextIndex(name) # important we use getChild here, so that includes are resolved # correctly - self._current = self._current.getChild(name, create=True) + self._current = self._current.getChild(name, index, create=True) + + self._stateStack.append(ParseState()) + + if 'include' in attrs.keys(): + self.handleInclude(attrs['include']) self._currentTy = None; if 'type' in attrs.keys(): @@ -229,6 +252,7 @@ class PropsHandler(handler.ContentHandler): self._current = self._current.parent self._content = None self._currentTy = None + self._stateStack.pop() def parsePropsBool(self, content): diff --git a/catalog/testData/bad-index.xml b/catalog/testData/bad-index.xml new file mode 100644 index 0000000..1c7650d --- /dev/null +++ b/catalog/testData/bad-index.xml @@ -0,0 +1,11 @@ + + + + + + + 225000 + + + + diff --git a/catalog/testData/props1.xml b/catalog/testData/props1.xml new file mode 100644 index 0000000..b9cda22 --- /dev/null +++ b/catalog/testData/props1.xml @@ -0,0 +1,25 @@ + + + 42 + + + apple + + + + lemon + + + + pear + + + + + a + b + c + d + + + diff --git a/catalog/testData/props2.xml b/catalog/testData/props2.xml new file mode 100644 index 0000000..a7289e2 --- /dev/null +++ b/catalog/testData/props2.xml @@ -0,0 +1,20 @@ + + + 33 + + + apple + + + + lemon + + + + + + 99 + + + + diff --git a/catalog/testData/root-include.xml b/catalog/testData/root-include.xml new file mode 100644 index 0000000..71ff5f2 --- /dev/null +++ b/catalog/testData/root-include.xml @@ -0,0 +1,22 @@ + + + 42 + 43 + 44 + + + + + + + + + + + + + + + + + diff --git a/catalog/testData/sub-include.xml b/catalog/testData/sub-include.xml new file mode 100644 index 0000000..d330f67 --- /dev/null +++ b/catalog/testData/sub-include.xml @@ -0,0 +1,6 @@ + + + 42 + 43 + 44 + diff --git a/catalog/test_sgprops.py b/catalog/test_sgprops.py new file mode 100755 index 0000000..f731717 --- /dev/null +++ b/catalog/test_sgprops.py @@ -0,0 +1,61 @@ +import unittest + +import types +import sgprops + +class SGProps(unittest.TestCase): + + def test_parse(self): + parsed = sgprops.readProps("testData/props1.xml") + + self.assertEqual(parsed.getValue("value"), 42) + self.assertEqual(type(parsed.getValue("value")), types.IntType) + + valNode = parsed.getChild("value") + self.assertEqual(valNode.parent, parsed) + self.assertEqual(valNode.name, "value") + + self.assertEqual(valNode.value, 42) + self.assertEqual(type(valNode.value), types.IntType) + + with self.assertRaises(IndexError): + missingNode = parsed.getChild("missing") + + things = parsed.getChildren("thing") + self.assertEqual(len(things), 3) + + self.assertEqual(things[0], parsed.getChild("thing", 0)); + self.assertEqual(things[1], parsed.getChild("thing", 1)); + self.assertEqual(things[2], parsed.getChild("thing", 2)); + + self.assertEqual(things[0].getValue("value"), "apple"); + self.assertEqual(things[1].getValue("value"), "lemon"); + self.assertEqual(things[2].getValue("value"), "pear"); + + def test_create(self): + pass + + + def test_invalidIndex(self): + with self.assertRaises(IndexError): + parsed = sgprops.readProps("testData/bad-index.xml") + + def test_include(self): + parsed = sgprops.readProps("testData/props2.xml") + + # test that value in main file over-rides the one in the include + self.assertEqual(parsed.getValue("value"), 33) + + # but these come from the included file + self.assertEqual(parsed.getValue("value[1]"), 43) + self.assertEqual(parsed.getValue("value[2]"), 44) + + subNode = parsed.getChild("sub") + widgets = subNode.getChildren("widget") + self.assertEqual(len(widgets), 4) + + self.assertEqual(widgets[2].value, 44) + self.assertEqual(widgets[3].value, 99) + +if __name__ == '__main__': + unittest.main() diff --git a/catalog/update-catalog.py b/catalog/update-catalog.py index 8b9ce22..bc1c52c 100755 --- a/catalog/update-catalog.py +++ b/catalog/update-catalog.py @@ -10,6 +10,7 @@ import shutil import subprocess import time import sgprops +import sys CATALOG_VERSION = 4 @@ -125,7 +126,7 @@ def scan_aircraft_dir(aircraft_dir): if d == None: continue except: - print "Skipping set file since couldn't be parsed:", os.path.join(aircraft_dir, file) + print "Skipping set file since couldn't be parsed:", os.path.join(aircraft_dir, file), sys.exc_info()[0] continue setDicts.append(d)