| 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);
 |