state.py 5.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. # coding: utf-8
  2. import typing
  3. from _elementtree import Element
  4. from lxml import etree
  5. from synergine2.config import Config
  6. from synergine2.log import get_logger
  7. from opencombat.exception import StateLoadError
  8. from opencombat.simulation.base import TileStrategySimulation
  9. from opencombat.simulation.subject import TileSubject
  10. from opencombat.util import get_class_from_string_path
  11. from opencombat.util import get_text_xml_element
  12. class State(object):
  13. def __init__(
  14. self,
  15. config: Config,
  16. state_root: Element,
  17. simulation: TileStrategySimulation,
  18. ) -> None:
  19. self._config = config
  20. self._state_root = state_root
  21. self._subjects = None # type: typing.List[TileSubject]
  22. self._simulation = simulation
  23. @property
  24. def subjects(self) -> typing.List[TileSubject]:
  25. if self._subjects is None:
  26. self._subjects = self._get_subjects()
  27. return self._subjects
  28. def _get_subjects(self) -> typing.List[TileSubject]:
  29. subjects = []
  30. subject_elements = self._state_root.find('subjects').findall('subject')
  31. for subject_element in subject_elements:
  32. subject_class_path = subject_element.find('type').text
  33. subject_class = get_class_from_string_path(
  34. self._config,
  35. subject_class_path,
  36. )
  37. subject = subject_class(self._config, self._simulation)
  38. self._fill_subject(subject, subject_element)
  39. # TODO BS 2018-06-13: Fill subject with property
  40. subjects.append(subject)
  41. return subjects
  42. def _fill_subject(
  43. self,
  44. subject: TileSubject,
  45. subject_element: Element,
  46. ) -> None:
  47. subject.position = tuple(
  48. map(
  49. int,
  50. get_text_xml_element(subject_element, 'position').split(','),
  51. ),
  52. )
  53. subject.direction = float(
  54. get_text_xml_element(subject_element, 'direction'),
  55. )
  56. """
  57. TODO:
  58. properties={
  59. SELECTION_COLOR_RGB: DE_COLOR,
  60. FLAG: FLAG_DE,
  61. SIDE: 'AXIS',
  62. }
  63. """
  64. class StateLoader(object):
  65. def __init__(
  66. self,
  67. config: Config,
  68. simulation: TileStrategySimulation,
  69. ) -> None:
  70. self._logger = get_logger('StateLoader', config)
  71. self._config = config
  72. self._simulation = simulation
  73. def get_state(
  74. self,
  75. state_file_path: str,
  76. ) -> State:
  77. return State(
  78. self._config,
  79. self._validate_and_return_state_element(state_file_path),
  80. self._simulation,
  81. )
  82. def _validate_and_return_state_element(
  83. self,
  84. state_file_path: str,
  85. ) -> Element:
  86. # open and read schema file
  87. schema_file_path = self._config.get(
  88. 'global.state_schema',
  89. 'opencombat/state.xsd',
  90. )
  91. with open(schema_file_path, 'r') as schema_file:
  92. schema_to_check = schema_file.read()
  93. # open and read xml file
  94. with open(state_file_path, 'r') as xml_file:
  95. xml_to_check = xml_file.read()
  96. xmlschema_doc = etree.fromstring(schema_to_check.encode('utf-8'))
  97. xmlschema = etree.XMLSchema(xmlschema_doc)
  98. try:
  99. doc = etree.fromstring(xml_to_check.encode('utf-8'))
  100. # check for file IO error
  101. except IOError as exc:
  102. self._logger.error(exc)
  103. raise StateLoadError('Invalid File "{}": {}'.format(
  104. state_file_path,
  105. str(exc),
  106. ))
  107. # check for XML syntax errors
  108. except etree.XMLSyntaxError as exc:
  109. self._logger.error(exc)
  110. raise StateLoadError('XML Syntax Error in "{}": {}'.format(
  111. state_file_path,
  112. str(exc.error_log),
  113. ))
  114. except Exception as exc:
  115. self._logger.error(exc)
  116. raise StateLoadError('Unknown error with "{}": {}'.format(
  117. state_file_path,
  118. str(exc),
  119. ))
  120. # validate against schema
  121. try:
  122. xmlschema.assertValid(doc)
  123. except etree.DocumentInvalid as exc:
  124. self._logger.error(exc)
  125. raise StateLoadError(
  126. 'Schema validation error with "{}": {}'.format(
  127. state_file_path,
  128. str(exc),
  129. )
  130. )
  131. except Exception as exc:
  132. self._logger.error(exc)
  133. raise StateLoadError(
  134. 'Unknown validation error with "{}": {}'.format(
  135. state_file_path,
  136. str(exc),
  137. )
  138. )
  139. return doc
  140. class StateLoaderBuilder(object):
  141. def __init__(
  142. self,
  143. config: Config,
  144. simulation: TileStrategySimulation,
  145. ) -> None:
  146. self._logger = get_logger('StateLoader', config)
  147. self._config = config
  148. self._simulation = simulation
  149. def get_state_loader(
  150. self,
  151. ) -> StateLoader:
  152. class_address = self._config.resolve(
  153. 'global.state_loader',
  154. 'opencombat.state.StateLoader',
  155. )
  156. state_loader_class = get_class_from_string_path(
  157. self._config,
  158. class_address,
  159. )
  160. return state_loader_class(
  161. self._config,
  162. self._simulation,
  163. )