php__phplib/OSInet/Finite_State_Machine/DiaLoader.php
Frederic G. MARAND a28ed41155 FSM: PSR-0 conversion, Grapher added.
- converted class names and directories for PSR0
- replaced the basic __autoload() by the canonical PSR0 loader example.
- added a GraphViz-based Grapher class to plot Machine structures.
2012-04-18 00:10:46 +02:00

277 lines
7.4 KiB
PHP

<?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\Finite_State_Machine;
$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);