DiaLoader.php 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. <?php
  2. /**
  3. * Input converter for FSM from Dia
  4. *
  5. * This class converts an UML diagram from Dia 0.9.6 into an abstract FSM graph
  6. * which can then be output by another class to the FSM XML format used by
  7. * the Osinet FSM 1.6.
  8. *
  9. * @copyright (c) 2007-2012 Ouest Systèmes Informatiques
  10. * @author Frederic G. MARAND
  11. * @license CeCILL 2.0
  12. * @link http://wiki.audean.com/fsm/fsm
  13. * @since FSM 1.6
  14. */
  15. namespace Osinet\FiniteStateMachine;
  16. $erFsmFromDia = error_reporting(E_ALL|E_STRICT);
  17. /**
  18. * This class converts an UML diagram from Dia 0.9.6 into an abstract FSM graph.
  19. *
  20. * @todo Validate the diagram: currently it will just choke on non-accepted diagrams.
  21. */
  22. class DiaLoader {
  23. const DIA_NAMESPACE = "http://www.lysator.liu.se/~alla/dia/";
  24. /**
  25. * The DOM for the source Dia diagram.
  26. *
  27. * @var \DOMDocument
  28. */
  29. protected $dom;
  30. /**
  31. * The FSM writer instance to save to.
  32. *
  33. * @var Writer
  34. */
  35. protected $fsm;
  36. /**
  37. * Sequence generator for unnamed event results (guards).
  38. *
  39. * @var integer
  40. */
  41. protected $resultGenerator = 0;
  42. /**
  43. * Extract the initial/final status from a Dia "UML - State Term".
  44. *
  45. * @param DOMElement $element
  46. *
  47. * @return array
  48. */
  49. protected function getStateTermInfo($element) {
  50. $query = 'dia:attribute[@name="is_final"]/dia:boolean';
  51. $xpath = new \DOMXPath($this->dom);
  52. $xpath->registerNamespace("dia", self::DIA_NAMESPACE);
  53. $id = $element->getAttribute('id');
  54. $diaAttributes = $xpath->query($query, $element);
  55. // Normal case
  56. if ($diaAttributes->length == 1) {
  57. // "is_final" is a Dia boolean
  58. $diaBoolean = $diaAttributes->item(0);
  59. $val = $diaBoolean->getAttribute('val');
  60. switch ($val) {
  61. case 'true':
  62. $ret = Machine::FINAL_STATE;
  63. break;
  64. case 'false':
  65. $ret = Machine::INIT_STATE;
  66. break;
  67. default:
  68. $ret = "anomalous($val)";
  69. break;
  70. }
  71. }
  72. else {
  73. echo "Initial/final state #$id does not bear the is_final attribute: anomaly.\n";
  74. }
  75. return array('type' => $ret, 'name' => $ret);
  76. }
  77. /**
  78. * Extract the name from a Dia "UML - State".
  79. *
  80. * @param DOMElement $element
  81. *
  82. * @return array
  83. */
  84. protected function getStateInfo($element) {
  85. $query = 'dia:attribute[@name="text"]/dia:composite/dia:attribute[@name="string"]/dia:string';
  86. $xpath = new \DOMXPath($this->dom);
  87. $xpath->registerNamespace("dia", self::DIA_NAMESPACE);
  88. $id = $element->getAttribute('id');
  89. $diaString = $xpath->query($query, $element);
  90. // Normal case
  91. if ($diaString->length == 1) {
  92. $diaText = $diaString->item(0);
  93. $ret = trim($diaText->textContent, '#');
  94. }
  95. else {
  96. echo "Standard state #$id does not contain the expected content: anomaly.\n";
  97. }
  98. return array('type' => 'standard', 'name' => $ret);
  99. }
  100. /**
  101. * Extract the actual text content from a query for a Dia string element.
  102. *
  103. * This is to be used over XPath query results
  104. *
  105. * @param DOMNodeList $nodes
  106. *
  107. * @return string
  108. */
  109. private function getTextFromDiaString ($nodes) {
  110. if ($nodes->length == 1) {
  111. $ret = $nodes->item(0);
  112. $ret = trim($ret->textContent, '#');
  113. }
  114. else {
  115. $ret = NULL;
  116. }
  117. return $ret;
  118. }
  119. /**
  120. * Extract the various information fields from a Dia "UML - Connection".
  121. *
  122. * @param DOMElement $element
  123. *
  124. * @return array
  125. */
  126. protected function getTransitionInfo($element) {
  127. $xpath = new \DOMXPath($this->dom);
  128. $xpath->registerNamespace("dia", self::DIA_NAMESPACE );
  129. $id = $element->getAttribute('id');
  130. $baseQuery = 'dia:attribute[@name="%s"]/dia:string';
  131. foreach (array('trigger', 'action', 'guard') as $parameter) {
  132. $query = sprintf($baseQuery, $parameter);
  133. $nodes = $xpath->query($query, $element);
  134. $ret[$parameter] = $this->getTextFromDiaString($nodes);
  135. // Triggers and actions.
  136. if ($parameter <> 'guard') {
  137. $ret[$parameter] = ucfirst($ret[$parameter]);
  138. }
  139. }
  140. $query = 'dia:connections/dia:connection';
  141. $diaConnections = $xpath->query($query, $element);
  142. // echo "Connections: {$dia_connections->length}\n";
  143. if ($diaConnections->length == 2) {
  144. // echo "Transition $id links" ;
  145. foreach ($diaConnections as $end) {
  146. $handle = $end->getAttribute('handle');
  147. $link = $end->getAttribute('to');
  148. switch ($handle) {
  149. case '0' :
  150. $kind = 'from';
  151. $ret['from'] = $link ;
  152. break;
  153. case '1' :
  154. $kind = 'to';
  155. $ret['to'] = $link ;
  156. break;
  157. default:
  158. $kind = "anomaly($handle)";
  159. break;
  160. }
  161. // echo " $kind $link";
  162. }
  163. }
  164. else {
  165. echo "Anomaly detected on the connection properties of transition #$id.\n";
  166. }
  167. // echo " on ${trigger}[$guard]/$action.\n";
  168. return $ret;
  169. }
  170. /**
  171. * Load a Dia file into the DOM
  172. *
  173. * @param string $filePath
  174. *
  175. * @return void
  176. */
  177. public function __construct($filePath) {
  178. $this->dom = new \DOMDocument();
  179. $this->dom->load($filePath);
  180. $this->fsm = new Writer();
  181. }
  182. /**
  183. * Parse the DOM to extract the various UML elements to an abstract FSM array.
  184. *
  185. * @return array
  186. */
  187. public function parse() {
  188. $query = '//dia:object';
  189. $xpath = new \DOMXPath($this->dom);
  190. $xpath->registerNamespace("dia", self::DIA_NAMESPACE);
  191. $result = $xpath->query($query);
  192. foreach ($result as $object) {
  193. $type = $object->getAttribute('type');
  194. $id = $object->getAttribute('id');
  195. switch ($type) {
  196. case 'UML - State Term':
  197. $state = $this->getStateTermInfo($object);
  198. $this->fsm->addState($state['name']);
  199. $this->states[$id] = $state; // needed to match transitions
  200. if (($state['type'] == Machine::INIT_STATE) || ($state['type'] == Machine::FINAL_STATE)) {
  201. $this->fsm->setSpecialState($state['type'], $state['name']);
  202. }
  203. break;
  204. case 'UML - State':
  205. $state = $this->getStateInfo($object);
  206. $this->fsm->addState($state['name']);
  207. // Needed to match transitions.
  208. $this->states[$id] = $state;
  209. break;
  210. case 'UML - Transition':
  211. $transition = $this->getTransitionInfo($object);
  212. $stateName = $this->states[$transition['from']]['name'];
  213. $nextStateName = $this->states[$transition['to']]['name'];
  214. $eventName = $transition['trigger'];
  215. $resultName = $transition['guard'];
  216. $actionName = $transition['action'];
  217. // Not allowed.
  218. if (empty($resultName)) {
  219. $resultName = "unnnamed_result_" . $this->resultGenerator++;
  220. }
  221. // This add will fail when adding outcomes to existing events, but
  222. // this is as designed.
  223. $this->fsm->addEvent($stateName, $eventName);
  224. $this->fsm->addOutcome($stateName, $eventName, $resultName,
  225. $nextStateName, $actionName);
  226. break;
  227. default:
  228. echo "Object #$id is of unknown type $type: ignored.\n";
  229. break;
  230. }
  231. }
  232. }
  233. /**
  234. * Facade for Writer.
  235. *
  236. * @param string $prefix
  237. * @param boolean $php
  238. * @param boolean $overwrite
  239. */
  240. public function saveFsm($prefix = 'fsm', $php = FALSE, $overwrite = FALSE) {
  241. $this->fsm->saveFsm($prefix, $php, $overwrite);
  242. }
  243. }
  244. error_reporting($erFsmFromDia);
  245. unset ($erFsmFromDia);