public static function loadString($data_string, $options = array(), $filename = null) { // Options $graphml_group_name = isset($options['group']) ? $options['group'] : null; // Graph $keys = array(); $nodes = array(); $edges = array(); // Load GraphML into DOM $dom = new \DOMDocument(); $dom->loadXml($data_string); // Prepare XPath query engine $xpath = new \DOMXpath($dom); $xpath->registerNameSpace('g', 'http://graphml.graphdrawing.org/xmlns'); // Find group node if ($graphml_group_name) { $root_graph = null; foreach ($xpath->query('//g:graph') as $el) { foreach ($xpath->query('../g:data/*/*/y:GroupNode/y:NodeLabel', $el) as $label_el) { $label = trim($label_el->textContent); if ($label == $graphml_group_name) { $root_graph = $el; break 2; } } } } else { $root_graph = $xpath->query('/g:graphml/g:graph')->item(0); } if ($root_graph == null) { throw new GraphMLException('Graph node not found.'); } // Load keys foreach ($xpath->query('./g:key[@attr.name][@id]') as $el) { $id = $el->attributes->getNamedItem('id')->value; $name = $el->attributes->getNamedItem('attr.name')->value; //debug_msg("tag> %s => %s", $id, $name); $keys[$id] = $name; } // Load graph properties $graph_props = array(); foreach ($xpath->query('./g:data[@key]', $root_graph) as $data_el) { $k = $data_el->attributes->getNamedItem('key')->value; if (isset($keys[$k])) { if ($keys[$k] == 'Properties') { // Special handling of machine properties $properties = array(); foreach ($xpath->query('./property[@name]', $data_el) as $property_el) { $property_name = $property_el->attributes->getNamedItem('name')->value; foreach ($property_el->attributes as $property_attr_name => $property_attr) { $properties[$property_name][$property_attr_name] = $property_attr->value; } } $graph_props['properties'] = $properties; } else { $graph_props[static::str2key($keys[$k])] = trim($data_el->textContent); } } } //debug_dump($graph_props, '$graph_props'); // Load nodes foreach ($xpath->query('.//g:node[@id]', $root_graph) as $el) { $id = $el->attributes->getNamedItem('id')->value; $node_props = array(); foreach ($xpath->query('.//g:data[@key]', $el) as $data_el) { $k = $data_el->attributes->getNamedItem('key')->value; if (isset($keys[$k])) { $node_props[static::str2key($keys[$k])] = $data_el->textContent; } } $label = $xpath->query('.//y:NodeLabel', $el)->item(0)->textContent; if ($label !== null) { $node_props['label'] = trim($label); } $color = $xpath->query('.//y:Fill', $el)->item(0)->attributes->getNamedItem('color')->value; if ($color !== null) { $node_props['color'] = trim($color); } //debug_msg("node> %s: \"%s\"", $id, @ $node_props['state']); //debug_dump($node_props, '$node_props'); $nodes[$id] = $node_props; } // Load edges foreach ($xpath->query('//g:graph/g:edge[@id][@source][@target]') as $el) { $id = $el->attributes->getNamedItem('id')->value; $source = $el->attributes->getNamedItem('source')->value; $target = $el->attributes->getNamedItem('target')->value; if (!isset($nodes[$source]) || !isset($nodes[$target])) { continue; } $edge_props = array(); foreach ($xpath->query('.//g:data[@key]', $el) as $data_el) { $k = $data_el->attributes->getNamedItem('key')->value; if (isset($keys[$k])) { $edge_props[static::str2key($keys[$k])] = $data_el->textContent; } } $label_query_result = $xpath->query('.//y:EdgeLabel', $el)->item(0); if (!$label_query_result) { throw new GraphMLException(sprintf('Missing edge label. Edge: %s -> %s', isset($nodes[$source]['label']) ? $nodes[$source]['label'] : $source, isset($nodes[$target]['label']) ? $nodes[$target]['label'] : $target)); } $label = $label_query_result->textContent; if ($label !== null) { $edge_props['label'] = trim($label); } $color_query_result = $xpath->query('.//y:LineStyle', $el)->item(0); if ($color_query_result) { $color = $color_query_result->attributes->getNamedItem('color')->value; if ($color !== null) { $edge_props['color'] = trim($color); } } //debug_msg("edge> %s: %s -> %s", $id, $source, $target); //debug_dump($edge_props, '$edge_props'); $edges[$id] = array($source, $target, $edge_props); } // Build machine definition $machine = array('_' => "<?php printf('_%c%c}%c',34,10,10);__halt_compiler();?>"); // Graph properties foreach ($graph_props as $k => $v) { $machine[$k] = $v; } // Store states foreach ($nodes as &$n) { if (empty($n['state'])) { if (empty($n['label'])) { // Skip 'nonexistent' state, it is present by default $n['state'] = ''; continue; } else { // Use label as state name $n['state'] = (string) $n['label']; } } if (empty($n['label'])) { $n['label'] = $n['state']; } $machine['states'][(string) $n['state']] = $n; } // Store actions and transitions foreach ($edges as $e) { list($source_id, $target_id, $props) = $e; $source = $source_id != '' ? isset($nodes[$source_id]) ? (string) $nodes[$source_id]['state'] : null : ''; $target = $target_id != '' ? isset($nodes[$target_id]) ? (string) $nodes[$target_id]['state'] : null : ''; if ($source === null || $target === null) { // Ignore nonexistent nodes continue; } if (@$props['action'] != '') { $action = $props['action']; } else { if (@$props['label'] != '') { $action = $props['label']; } else { throw new GraphMLException(sprintf('Missing label at edge "%s" -> "%s".', $nodes[$source]['label'], $nodes[$target]['label'])); } } $tr =& $machine['actions'][$action]['transitions'][$source]; foreach ($props as $k => $v) { $tr[$k] = $v; } $tr['targets'][] = $target; unset($tr); } // Sort stuff to keep them in order when file is modified asort($machine['states']); asort($machine['actions']); //debug_dump($machine['states'], 'States'); //debug_dump($machine['actions'], 'Actions'); //debug_dump($machine, '$machine'); return $machine; }
/** * Execute an XPath query and return the resulting elements * * @access public * @param string XPath * @param bool Return only the first result * @return mixed Array if !$return_first, cortex_xml object or null */ public function get_xpath($path, $return_first = false) { $xpath = new DOMXpath($this->document); // Take care of the default namespace, DOMXpath doesn't support this if (!is_null($this->default_ns)) { $path = preg_replace('#/([^:/]+?)#', '/doc-ns:$1', $path); $xpath->registerNameSpace('doc-ns', $this->default_ns); } $nodes = $this->parse_children($xpath->query($path)); if ($return_first) { return sizeof($nodes) > 0 ? $nodes[0] : null; } return $nodes; }
public static function loadString($data_string, $options = [], $filename = null) { // Options $bpmn_process_id = isset($options['process_id']) ? $options['process_id'] : null; $state_machine_participant_id = isset($options['state_machine_participant_id']) ? $options['state_machine_participant_id'] : null; // Load GraphML into DOM $dom = new \DOMDocument(); $dom->loadXml($data_string); // Prepare XPath query engine $xpath = new \DOMXpath($dom); $xpath->registerNameSpace('bpmn', 'http://www.omg.org/spec/BPMN/20100524/MODEL'); if ($bpmn_process_id) { if (!preg_match('/^[a-zA-Z0-9_.-]*$/', $bpmn_process_id)) { throw new BpmnException('Invalid process ID (only alphanumeric characters, underscore, dot and dash are allowed): ' . var_export($bpmn_process_id, true)); } $machine_process_element = $xpath->query('/bpmn:definitions/bpmn:process[@id=\'' . $bpmn_process_id . '\']')->item(0); } else { $machine_process_element = $xpath->query('/bpmn:definitions/bpmn:process')->item(0); } // Process element is mandatory if (!$machine_process_element) { throw new BpmnException('Process element not found: ' . var_export($bpmn_process_id, true)); } // Lets collect arrows, events and tasks (still in BPMN semantics) $arrows = []; $nodes = []; $groups = []; // Get processes (groups) foreach ($xpath->query('//bpmn:process[@id]') as $el) { $id = $el->getAttribute('id'); $name = $el->getAttribute('name'); $groups[$id] = ['id' => $id, 'name' => $name == '' ? $id : $name, 'nodes' => []]; } // Get arrows foreach (['sequenceFlow', 'messageFlow'] as $type) { foreach ($xpath->query('//bpmn:' . $type . '[@id][@sourceRef][@targetRef]') as $el) { // Arrow properties $id = $el->getAttribute('id'); $name = $el->getAttribute('name'); $sourceRef = $el->getAttribute('sourceRef'); $targetRef = $el->getAttribute('targetRef'); // Get process where the arrow belongs $process_element = $el->parentNode; if ($process_element && $process_element->tagName == 'bpmn:process') { $process_id = $process_element->getAttribute('id'); } else { $process_id = null; } // Store arrow $arrows[$id] = ['id' => $id, 'type' => $type, 'source' => $sourceRef, 'target' => $targetRef, 'name' => $name, 'process' => $process_id]; } } // Get nodes foreach (['participant', 'startEvent', 'task', 'intermediateThrowEvent', 'intermediateCatchEvent', 'endEvent', 'exclusiveGateway', 'parallelGateway', 'inclusiveGateway', 'complexGateway', 'eventBasedGateway'] as $type) { foreach ($xpath->query('//bpmn:' . $type . '[@id]') as $el) { $id = $el->getAttribute('id'); $name = $el->getAttribute('name'); if ($name == '') { $name = $id; } // Get process where the node belongs if ($processRef = $el->getAttribute('processRef')) { $process_id = $processRef; } else { if (($process_element = $el->parentNode) && $process_element->tagName == 'bpmn:process') { $process_id = $process_element->getAttribute('id'); } else { $process_id = null; } } if ($process_id) { $groups[$process_id]['nodes'][] = $id; } $incoming = []; foreach ($xpath->query('./bpmn:incoming/text()[1]', $el) as $in) { $incoming[] = $in->wholeText; } $outgoing = []; foreach ($xpath->query('./bpmn:outgoing/text()[1]', $el) as $out) { $outgoing[] = $out->wholeText; } $nodes[$id] = ['id' => $id, 'name' => $name, 'type' => $type, 'process' => $process_id, 'incoming' => $incoming, 'outgoing' => $outgoing]; } } // Store fragment in state machine definition return ['bpmn_fragments' => [$filename . '#' . $bpmn_process_id => ['file' => $filename, 'process_id' => $bpmn_process_id, 'state_machine_participant_id' => $state_machine_participant_id, 'arrows' => $arrows, 'nodes' => $nodes, 'groups' => $groups]]]; ///--------------- // Dump BPMN fragment /* printf("\n<pre>BPMN diagram: %s, %s\n", basename($filename), $bpmn_process_id); printf(" Nodes:\n"); foreach ($nodes as $id => $n) { printf(" %s (%s)\n", var_export($n['name'], true), var_export($id, true)); foreach ($n['incoming'] as $in) { printf(" In: %s\n", $in); } foreach ($n['outgoing'] as $out) { printf(" Out: %s\n", $out); } } printf(" Arrows:\n"); foreach ($arrows as $id => $a) { printf(" %25s --> %-25s (%s, %s)\n", $a['source'], $a['target'], var_export($id, true), var_export($a['name'], true)); } printf("</pre>\n"); // */ }