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