123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277 |
- <?php
- /**
- * Input converter for FSM from Dia
- *
- * This class converts an UML diagram from Dia 0.9.6 into an abstract FSM graph
- * which can then be output by another class to the FSM XML format used by
- * the Osinet FSM 1.6.
- *
- * @copyright (c) 2007-2012 Ouest Systèmes Informatiques
- * @author Frederic G. MARAND
- * @license CeCILL 2.0
- * @link http://wiki.audean.com/fsm/fsm
- * @since FSM 1.6
- */
- namespace Osinet\FiniteStateMachine;
- $erFsmFromDia = error_reporting(E_ALL|E_STRICT);
- /**
- * This class converts an UML diagram from Dia 0.9.6 into an abstract FSM graph.
- *
- * @todo Validate the diagram: currently it will just choke on non-accepted diagrams.
- */
- class DiaLoader {
- const DIA_NAMESPACE = "http://www.lysator.liu.se/~alla/dia/";
- /**
- * The DOM for the source Dia diagram.
- *
- * @var \DOMDocument
- */
- protected $dom;
- /**
- * The FSM writer instance to save to.
- *
- * @var Writer
- */
- protected $fsm;
- /**
- * Sequence generator for unnamed event results (guards).
- *
- * @var integer
- */
- protected $resultGenerator = 0;
- /**
- * Extract the initial/final status from a Dia "UML - State Term".
- *
- * @param DOMElement $element
- *
- * @return array
- */
- protected function getStateTermInfo($element) {
- $query = 'dia:attribute[@name="is_final"]/dia:boolean';
- $xpath = new \DOMXPath($this->dom);
- $xpath->registerNamespace("dia", self::DIA_NAMESPACE);
- $id = $element->getAttribute('id');
- $diaAttributes = $xpath->query($query, $element);
- // Normal case
- if ($diaAttributes->length == 1) {
- // "is_final" is a Dia boolean
- $diaBoolean = $diaAttributes->item(0);
- $val = $diaBoolean->getAttribute('val');
- switch ($val) {
- case 'true':
- $ret = Machine::FINAL_STATE;
- break;
- case 'false':
- $ret = Machine::INIT_STATE;
- break;
- default:
- $ret = "anomalous($val)";
- break;
- }
- }
- else {
- echo "Initial/final state #$id does not bear the is_final attribute: anomaly.\n";
- }
- return array('type' => $ret, 'name' => $ret);
- }
- /**
- * Extract the name from a Dia "UML - State".
- *
- * @param DOMElement $element
- *
- * @return array
- */
- protected function getStateInfo($element) {
- $query = 'dia:attribute[@name="text"]/dia:composite/dia:attribute[@name="string"]/dia:string';
- $xpath = new \DOMXPath($this->dom);
- $xpath->registerNamespace("dia", self::DIA_NAMESPACE);
- $id = $element->getAttribute('id');
- $diaString = $xpath->query($query, $element);
- // Normal case
- if ($diaString->length == 1) {
- $diaText = $diaString->item(0);
- $ret = trim($diaText->textContent, '#');
- }
- else {
- echo "Standard state #$id does not contain the expected content: anomaly.\n";
- }
- return array('type' => 'standard', 'name' => $ret);
- }
- /**
- * Extract the actual text content from a query for a Dia string element.
- *
- * This is to be used over XPath query results
- *
- * @param DOMNodeList $nodes
- *
- * @return string
- */
- private function getTextFromDiaString ($nodes) {
- if ($nodes->length == 1) {
- $ret = $nodes->item(0);
- $ret = trim($ret->textContent, '#');
- }
- else {
- $ret = NULL;
- }
- return $ret;
- }
- /**
- * Extract the various information fields from a Dia "UML - Connection".
- *
- * @param DOMElement $element
- *
- * @return array
- */
- protected function getTransitionInfo($element) {
- $xpath = new \DOMXPath($this->dom);
- $xpath->registerNamespace("dia", self::DIA_NAMESPACE );
- $id = $element->getAttribute('id');
- $baseQuery = 'dia:attribute[@name="%s"]/dia:string';
- foreach (array('trigger', 'action', 'guard') as $parameter) {
- $query = sprintf($baseQuery, $parameter);
- $nodes = $xpath->query($query, $element);
- $ret[$parameter] = $this->getTextFromDiaString($nodes);
- // Triggers and actions.
- if ($parameter <> 'guard') {
- $ret[$parameter] = ucfirst($ret[$parameter]);
- }
- }
- $query = 'dia:connections/dia:connection';
- $diaConnections = $xpath->query($query, $element);
- // echo "Connections: {$dia_connections->length}\n";
- if ($diaConnections->length == 2) {
- // echo "Transition $id links" ;
- foreach ($diaConnections as $end) {
- $handle = $end->getAttribute('handle');
- $link = $end->getAttribute('to');
- switch ($handle) {
- case '0' :
- $kind = 'from';
- $ret['from'] = $link ;
- break;
- case '1' :
- $kind = 'to';
- $ret['to'] = $link ;
- break;
- default:
- $kind = "anomaly($handle)";
- break;
- }
- // echo " $kind $link";
- }
- }
- else {
- echo "Anomaly detected on the connection properties of transition #$id.\n";
- }
- // echo " on ${trigger}[$guard]/$action.\n";
- return $ret;
- }
- /**
- * Load a Dia file into the DOM
- *
- * @param string $filePath
- *
- * @return void
- */
- public function __construct($filePath) {
- $this->dom = new \DOMDocument();
- $this->dom->load($filePath);
- $this->fsm = new Writer();
- }
- /**
- * Parse the DOM to extract the various UML elements to an abstract FSM array.
- *
- * @return array
- */
- public function parse() {
- $query = '//dia:object';
- $xpath = new \DOMXPath($this->dom);
- $xpath->registerNamespace("dia", self::DIA_NAMESPACE);
- $result = $xpath->query($query);
- foreach ($result as $object) {
- $type = $object->getAttribute('type');
- $id = $object->getAttribute('id');
- switch ($type) {
- case 'UML - State Term':
- $state = $this->getStateTermInfo($object);
- $this->fsm->addState($state['name']);
- $this->states[$id] = $state; // needed to match transitions
- if (($state['type'] == Machine::INIT_STATE) || ($state['type'] == Machine::FINAL_STATE)) {
- $this->fsm->setSpecialState($state['type'], $state['name']);
- }
- break;
- case 'UML - State':
- $state = $this->getStateInfo($object);
- $this->fsm->addState($state['name']);
- // Needed to match transitions.
- $this->states[$id] = $state;
- break;
- case 'UML - Transition':
- $transition = $this->getTransitionInfo($object);
- $stateName = $this->states[$transition['from']]['name'];
- $nextStateName = $this->states[$transition['to']]['name'];
- $eventName = $transition['trigger'];
- $resultName = $transition['guard'];
- $actionName = $transition['action'];
- // Not allowed.
- if (empty($resultName)) {
- $resultName = "unnnamed_result_" . $this->resultGenerator++;
- }
- // This add will fail when adding outcomes to existing events, but
- // this is as designed.
- $this->fsm->addEvent($stateName, $eventName);
- $this->fsm->addOutcome($stateName, $eventName, $resultName,
- $nextStateName, $actionName);
- break;
- default:
- echo "Object #$id is of unknown type $type: ignored.\n";
- break;
- }
- }
- }
- /**
- * Facade for Writer.
- *
- * @param string $prefix
- * @param boolean $php
- * @param boolean $overwrite
- */
- public function saveFsm($prefix = 'fsm', $php = FALSE, $overwrite = FALSE) {
- $this->fsm->saveFsm($prefix, $php, $overwrite);
- }
- }
- error_reporting($erFsmFromDia);
- unset ($erFsmFromDia);
|