Browse Source

xml validator for unit and team stashs

Bastien Sevajol 5 years ago
parent
commit
2cf75d0155

+ 2 - 0
config.yaml View File

@@ -27,6 +27,8 @@ global:
27 27
     state_template: "opencombat/state_template.xml"
28 28
     unit_stash: "opencombat.strategy.unit.stash.UnitStash"
29 29
     team_stash: "opencombat.strategy.team.stash.TeamStash"
30
+    teams_schema: "opencombat/strategy/teams.xsd"
31
+    units_schema: "opencombat/strategy/units.xsd"
30 32
     cache_dir_path: 'cache'
31 33
     include_path:
32 34
       maps:

+ 11 - 60
opencombat/state.py View File

@@ -7,13 +7,13 @@ from lxml import etree
7 7
 from synergine2.config import Config
8 8
 from synergine2.log import get_logger
9 9
 
10
-from opencombat.exception import StateLoadError
11 10
 from opencombat.exception import NotFoundError
12 11
 from opencombat.simulation.base import TileStrategySimulation
13 12
 from opencombat.simulation.subject import TileSubject
14 13
 from opencombat.util import get_class_from_string_path
15 14
 from opencombat.util import pretty_xml
16 15
 from opencombat.util import get_text_xml_element
16
+from opencombat.xml import XmlValidator
17 17
 
18 18
 
19 19
 class State(object):
@@ -195,6 +195,15 @@ class StateLoader(object):
195 195
         self._config = config
196 196
         self._simulation = simulation
197 197
 
198
+        schema_file_path = self._config.get(
199
+            'global.state_schema',
200
+            'opencombat/state.xsd',
201
+        )
202
+        self._xml_validator = XmlValidator(
203
+            config,
204
+            schema_file_path,
205
+        )
206
+
198 207
     def get_state(
199 208
         self,
200 209
         state_file_path: str,
@@ -209,65 +218,7 @@ class StateLoader(object):
209 218
         self,
210 219
         state_file_path: str,
211 220
     ) -> Element:
212
-        # open and read schema file
213
-        schema_file_path = self._config.get(
214
-            'global.state_schema',
215
-            'opencombat/state.xsd',
216
-        )
217
-        with open(schema_file_path, 'r') as schema_file:
218
-            schema_to_check = schema_file.read()
219
-
220
-        # open and read xml file
221
-        with open(state_file_path, 'r') as xml_file:
222
-            xml_to_check = xml_file.read()
223
-
224
-        xmlschema_doc = etree.fromstring(schema_to_check.encode('utf-8'))
225
-        xmlschema = etree.XMLSchema(xmlschema_doc)
226
-
227
-        try:
228
-            doc = etree.fromstring(xml_to_check.encode('utf-8'))
229
-        # check for file IO error
230
-        except IOError as exc:
231
-            self._logger.error(exc)
232
-            raise StateLoadError('Invalid File "{}": {}'.format(
233
-                state_file_path,
234
-                str(exc),
235
-            ))
236
-        # check for XML syntax errors
237
-        except etree.XMLSyntaxError as exc:
238
-            self._logger.error(exc)
239
-            raise StateLoadError('XML Syntax Error in "{}": {}'.format(
240
-                state_file_path,
241
-                str(exc.error_log),
242
-            ))
243
-        except Exception as exc:
244
-            self._logger.error(exc)
245
-            raise StateLoadError('Unknown error with "{}": {}'.format(
246
-                state_file_path,
247
-                str(exc),
248
-            ))
249
-
250
-        # validate against schema
251
-        try:
252
-            xmlschema.assertValid(doc)
253
-        except etree.DocumentInvalid as exc:
254
-            self._logger.error(exc)
255
-            raise StateLoadError(
256
-                'Schema validation error with "{}": {}'.format(
257
-                    state_file_path,
258
-                    str(exc),
259
-                )
260
-            )
261
-        except Exception as exc:
262
-            self._logger.error(exc)
263
-            raise StateLoadError(
264
-                'Unknown validation error with "{}": {}'.format(
265
-                    state_file_path,
266
-                    str(exc),
267
-                )
268
-            )
269
-
270
-        return doc
221
+        return self._xml_validator.validate_and_return(state_file_path)
271 222
 
272 223
 
273 224
 class StateConstructorBuilder(object):

+ 11 - 2
opencombat/strategy/team/stash.py View File

@@ -4,6 +4,7 @@ import typing
4 4
 from synergine2.config import Config
5 5
 
6 6
 from opencombat.strategy.unit.model import TeamModel
7
+from opencombat.xml import XmlValidator
7 8
 
8 9
 
9 10
 class TeamStash(object):
@@ -12,10 +13,18 @@ class TeamStash(object):
12 13
         config: Config,
13 14
         teams_file_path: str,
14 15
     ) -> None:
15
-        self._confg = config
16
-        # TODO Load xml, validate
16
+        self._config = config
17 17
         self._teams = None  # typing.List[TeamModel]
18 18
 
19
+        schema_file_path = self._config.get(
20
+            'global.teams_schema',
21
+            'opencombat/strategy/teams.xsd',
22
+        )
23
+        self._xml_validator = XmlValidator(
24
+            config,
25
+            schema_file_path,
26
+        )
27
+
19 28
     def get_teams(self) -> typing.List[TeamModel]:
20 29
         pass
21 30
 

+ 8 - 3
opencombat/strategy/teams.xsd View File

@@ -1,6 +1,9 @@
1 1
 <?xml version="1.0" encoding="UTF-8" ?>
2 2
 <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
3
-    <xs:complexType name="teams">
3
+
4
+    <xs:element name="teams" type="teamstype"/>
5
+
6
+    <xs:complexType name="teamstype">
4 7
         <xs:sequence>
5 8
             <xs:element name="team" type="teamtype" maxOccurs="unbounded"/>
6 9
         </xs:sequence>
@@ -11,17 +14,19 @@
11 14
             <xs:element name="name" type="xs:string" maxOccurs="1"/>
12 15
             <xs:element name="units" type="unitstype" maxOccurs="1"/>
13 16
         </xs:sequence>
17
+        <xs:attribute name="id" type="xs:string" use="required"/>
18
+        <xs:attribute name="country" type="xs:string" use="required"/>
14 19
     </xs:complexType>
15 20
 
16 21
     <xs:complexType name="unitstype">
17 22
         <xs:sequence>
18
-            <xs:element name="unit" type="unittype" maxOccurs="unbounded"/>
23
+            <xs:element name="unit" type="unittype" maxOccurs="unbounded" minOccurs="1"/>
19 24
         </xs:sequence>
20 25
     </xs:complexType>
21 26
 
22 27
     <xs:complexType name="unittype">
23 28
         <xs:sequence>
24
-            <xs:element name="id" type="xs:string" maxOccurs="1"/>
29
+            <xs:element name="id" type="xs:string" maxOccurs="1" minOccurs="1"/>
25 30
         </xs:sequence>
26 31
     </xs:complexType>
27 32
 

+ 11 - 2
opencombat/strategy/unit/stash.py View File

@@ -4,6 +4,7 @@ import typing
4 4
 from synergine2.config import Config
5 5
 
6 6
 from opencombat.strategy.team.model import UnitModel
7
+from opencombat.xml import XmlValidator
7 8
 
8 9
 
9 10
 class UnitStash(object):
@@ -12,10 +13,18 @@ class UnitStash(object):
12 13
         config: Config,
13 14
         units_file_path: str,
14 15
     ) -> None:
15
-        self._confg = config
16
-        # TODO Load xml, validate
16
+        self._config = config
17 17
         self._units = None  # typing.List[UnitModel]
18 18
 
19
+        schema_file_path = self._config.get(
20
+            'global.teams_schema',
21
+            'opencombat/strategy/teams.xsd',
22
+        )
23
+        self._xml_validator = XmlValidator(
24
+            config,
25
+            schema_file_path,
26
+        )
27
+
19 28
     def get_units(self) -> typing.List[UnitModel]:
20 29
         pass
21 30
 

+ 21 - 0
opencombat/strategy/units.xsd View File

@@ -0,0 +1,21 @@
1
+<?xml version="1.0" encoding="UTF-8" ?>
2
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
3
+
4
+    <xs:element name="units" type="unitstype"/>
5
+
6
+    <xs:complexType name="unitstype">
7
+        <xs:sequence>
8
+            <xs:element name="unit" type="unittype" maxOccurs="unbounded" minOccurs="1"/>
9
+        </xs:sequence>
10
+    </xs:complexType>
11
+
12
+    <xs:complexType name="unittype">
13
+        <xs:sequence>
14
+            <xs:element name="name" type="xs:string" maxOccurs="1" minOccurs="1"/>
15
+            <xs:element name="type" type="xs:string" maxOccurs="1" minOccurs="1"/>
16
+        </xs:sequence>
17
+        <xs:attribute name="id" type="xs:string" use="required"/>
18
+        <xs:attribute name="country" type="xs:string" use="required"/>
19
+    </xs:complexType>
20
+
21
+</xs:schema>

+ 75 - 0
opencombat/xml.py View File

@@ -0,0 +1,75 @@
1
+# coding: utf-8
2
+from _elementtree import Element
3
+
4
+from lxml import etree
5
+from synergine2.config import Config
6
+from synergine2.log import get_logger
7
+
8
+from opencombat.exception import StateLoadError
9
+
10
+
11
+class XmlValidator(object):
12
+    def __init__(
13
+        self,
14
+        config: Config,
15
+        schema_file_path: str,
16
+    ) -> None:
17
+        self._config = config
18
+        self._logger = get_logger('XmlValidator', config)
19
+        self._schema_file_path = schema_file_path
20
+
21
+    def validate_and_return(self, xml_file_path: str) -> Element:
22
+        with open(self._schema_file_path, 'r') as schema_file:
23
+            schema_to_check = schema_file.read()
24
+
25
+        # open and read xml file
26
+        with open(xml_file_path, 'r') as xml_file:
27
+            xml_to_check = xml_file.read()
28
+
29
+        xmlschema_doc = etree.fromstring(schema_to_check.encode('utf-8'))
30
+        xmlschema = etree.XMLSchema(xmlschema_doc)
31
+
32
+        try:
33
+            doc = etree.fromstring(xml_to_check.encode('utf-8'))
34
+        # check for file IO error
35
+        except IOError as exc:
36
+            self._logger.error(exc)
37
+            raise StateLoadError('Invalid File "{}": {}'.format(
38
+                xml_file_path,
39
+                str(exc),
40
+            ))
41
+        # check for XML syntax errors
42
+        except etree.XMLSyntaxError as exc:
43
+            self._logger.error(exc)
44
+            raise StateLoadError('XML Syntax Error in "{}": {}'.format(
45
+                xml_file_path,
46
+                str(exc.error_log),
47
+            ))
48
+        except Exception as exc:
49
+            self._logger.error(exc)
50
+            raise StateLoadError('Unknown error in "{}": {}'.format(
51
+                xml_file_path,
52
+                str(exc),
53
+            ))
54
+
55
+        # validate against schema
56
+        try:
57
+            xmlschema.assertValid(doc)
58
+        except etree.DocumentInvalid as exc:
59
+            self._logger.error(exc)
60
+            raise StateLoadError(
61
+                'Schema validation error in "{}": {}'.format(
62
+                    xml_file_path,
63
+                    str(exc),
64
+                )
65
+            )
66
+        except Exception as exc:
67
+            self._logger.error(exc)
68
+            raise StateLoadError(
69
+                'Unknown validation error in "{}": {}'.format(
70
+                    xml_file_path,
71
+                    str(exc),
72
+                )
73
+            )
74
+
75
+        return doc

+ 8 - 0
test_config.yaml View File

@@ -21,6 +21,14 @@ game:
21 21
       draw_interior_gap: 2
22 22
 
23 23
 global:
24
+    state_loader: "opencombat.state.StateLoader"
25
+    state_dumper: "opencombat.state.StateDumper"
26
+    state_schema: "opencombat/state.xsd"
27
+    state_template: "opencombat/state_template.xml"
28
+    unit_stash: "opencombat.strategy.unit.stash.UnitStash"
29
+    team_stash: "opencombat.strategy.team.stash.TeamStash"
30
+    teams_schema: "opencombat/strategy/teams.xsd"
31
+    units_schema: "opencombat/strategy/units.xsd"
24 32
     cache_dir_path: 'cache'
25 33
     include_path:
26 34
       maps:

+ 0 - 0
tests/strategy/__init__.py View File


+ 13 - 0
tests/strategy/test_teams.py View File

@@ -0,0 +1,13 @@
1
+# coding: utf-8
2
+from synergine2.config import Config
3
+
4
+from opencombat.strategy.team.stash import TeamStash
5
+
6
+
7
+def test_units_stash__ok__instantiate(
8
+    config: Config,
9
+):
10
+    TeamStash(
11
+        config,
12
+        'opencombat/strategy/teams.xml',
13
+    )

+ 13 - 0
tests/strategy/test_units.py View File

@@ -0,0 +1,13 @@
1
+# coding: utf-8
2
+from synergine2.config import Config
3
+
4
+from opencombat.strategy.unit.stash import UnitStash
5
+
6
+
7
+def test_units_stash__ok__instantiate(
8
+    config: Config,
9
+):
10
+    UnitStash(
11
+        config,
12
+        'opencombat/strategy/units.xml',
13
+    )