123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283 |
- <?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 2007 Ouest Systèmes Informatiques
- * @author Frederic G. MARAND
- * @license CeCILL 2.0
- * @version CVS: $Id: Fsm_From_Dia.php,v 1.2 2007-06-10 19:38:14 marand Exp $
- * @link http://wiki.audean.com/fsm/fsm
- * @since FSM 1.6
- * @package fsm
- * @subpackage fsm.ui
- */
- $erFsmFromDia = error_reporting(E_ALL|E_STRICT);
- /**
- * This class converts an UML diagram from Dia 0.9.6
- * into an abstract FSM graph.
- *
- * @package fsm
- * @subpackage fsm.ui
- * @todo Validate the diagram: currently it will just choke on non-accepted diagrams
- */
- class Fsm_From_Dia
- {
- 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 FSM_Writer
- */
- protected $fsm;
- /**
- * Sequence generator for unnamed event results (guards)
- *
- * @var integer
- */
- protected $result_generator = 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", Fsm_From_Dia::DIA_NAMESPACE);
- $id = $element->getAttribute('id');
- $dia_attributes = $xpath->query($query, $element);
- if ($dia_attributes->length == 1) // Normal case
- {
- $dia_boolean = $dia_attributes->item(0); // "is_final" is a Dia boolean
- $val = $dia_boolean->getAttribute('val');
- switch ($val)
- {
- case 'true': $ret = Finite_State_Machine::FINAL_STATE; break;
- case 'false': $ret = Finite_State_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", Fsm_From_Dia::DIA_NAMESPACE);
- $id = $element->getAttribute('id');
- $dia_string = $xpath->query($query, $element);
- if ($dia_string->length == 1) // Normal case
- {
- $dia_text = $dia_string->item(0);
- $ret = trim($dia_text->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", Fsm_From_Dia::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);
- if ($parameter <> 'guard') // triggers and actions
- {
- $ret[$parameter] = ucfirst($ret[$parameter]);
- }
- }
- $query = 'dia:connections/dia:connection';
- $dia_connections = $xpath->query($query, $element);
- // echo "Connections: $dia_connections->length\n";
- if ($dia_connections->length == 2)
- {
- // echo "Transition $id links" ;
- foreach ($dia_connections 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 transiition #$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 Fsm_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", Fsm_From_Dia::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'] == Finite_State_Machine::INIT_STATE)
- || ($state['type'] == Finite_State_Machine::FINAL_STATE)
- )
- {
- $this->fsm->setSpecialState($state['type'], $state['name']);
- }
- break;
- case 'UML - State':
- $state = $this->getStateInfo($object);
- $this->fsm->addState($state['name']);
- $this->states[$id] = $state; // needed to match transitions
- break;
- case 'UML - Transition':
- $transition = $this->getTransitionInfo($object);
- $state_name = $this->states[$transition['from']]['name'];
- $next_state_name = $this->states[$transition['to']]['name'];
- $event_name = $transition['trigger'];
- $result_name = $transition['guard'];
- $action_name = $transition['action'];
- if (empty($result_name)) // Not allowed
- {
- $result_name = "unnnamed_result_" . $this->result_generator++;
- }
- /**
- * This add will fail when adding outcomes to existing events,
- * but this is as designed
- */
- $this->fsm->addEvent($state_name, $event_name);
- $this->fsm->addOutcome($state_name, $event_name, $result_name,
- $next_state_name, $action_name);
- break;
- default:
- echo "Object #$id is of unknown type $type: ignored.\n";
- break;
- }
- }
- }
- /**
- * Facade for FSM_Writer
- *
- * @param string $prefix
- * @param boolean $php
- * @param boolean $overwrite
- */
- public function save_fsm($prefix = 'fsm', $php = FALSE, $overwrite = FALSE)
- {
- $ret = $this->fsm->save_fsm($prefix = 'fsm', $php = FALSE, $overwrite = FALSE);
- return $ret;
- }
- }
- error_reporting($erFsmFromDia);
- unset ($erFsmFromDia);
|