simpleWorkflow
  • Class
  • Tree

Classes

  • SWActiveRecord
  • SWActiveRecordBehavior
  • SWComponent
  • SWEvent
  • SWException
  • SWHelper
  • SWNode
  • SWPhpWorkflowSource
  • SWValidator
  • SWWorkflowSource
  • SWyEdConverter
  • SWyEdConverterDOM
  1 <?php
  2 /**
  3  * Converts a workflow created with yEd Graph Editor (freeware) and saved in graphml format
  4  * into an array suitable to be used with the simpleWorkflow extension (sW).<br/>
  5  * The conversion is based on the $mapper array, which defines matches between sW attributes
  6  * and properties used by yEd.
  7  * Following sW values are references using a predefined name :
  8  * <ul>
  9  *      <li>workflow - initial : is the id of the initial node</li>
 10  *      <li>node - id : id of a given node</li>
 11  *      <li>node - constraint : PHP expression used as constraint for a node</li>
 12  *      <li>node - label : text label for a node</li>
 13  *      <li>node - metadata.* : custom metadata value</li>
 14  *      <li>edge - task : PHP expression executed when the transition is performed</li>
 15  * </ul>
 16  * yEd Graph Editor node attributes are referenced in two ways : by their attribute name or by
 17  * an xpath expression that applies to the y:ShapeNode element used by yEd to draw each node.
 18  * The later is mainly usefule to extract the node label and possibly, some other informations
 19  * like for instance the background color or text color used to render each node. The former
 20  * is useful to extract built-in yEd attribute and also custom attribute defined by the user.<br/>
 21  *
 22  */
 23 class SWyEdConverterDOM
 24 {
 25     /**
 26      * Maps a named attribute with a id attribute defined in the input graphml file.
 27      * @var array
 28      */
 29     private $_mapper = array(
 30     // workflow
 31     'w-intial-node-id' => "/ns:graphml/ns:key[@for='graph'][@attr.name='Initial-node-id']/@id",
 32 
 33     // nodes
 34     'n-constraint'     => "/ns:graphml/ns:key[@for='node'][@attr.name='Constraint']/@id",
 35     'n-label'          => "/ns:graphml/ns:key[@for='node'][@attr.name='Label']/@id",
 36     'n-graphics'       => "/ns:graphml/ns:key[@for='node'][@yfiles.type='nodegraphics']/@id",
 37 
 38     // edges
 39     'e-task'           => "/ns:graphml/ns:key[@for='edge'][@attr.name='Task']/@id",
 40     );
 41     /**
 42      * @var DOMDocument XML DOM parser
 43      */
 44     private $_dom;
 45     /**
 46      * @var DOMXPath The XPath object used to evaluate all xpath expressions
 47      */
 48     private $_xp;
 49     /**
 50      * @var array map between named properties and graphml properties extracted from the input
 51      * file. This array is builte based on the <em>_mapper</em> array.
 52      */
 53     private $_yedProperties = array();
 54     /**
 55      * Convert a graphml file describing a workflow into an array suitable to create a <em>simpleWorkflow</em>
 56      * object.
 57      * @param string $graphmlFile the path to the graphml file to process
 58      */
 59     public function convert($graphmlFile)
 60     {
 61         $this->_dom = new DOMDocument();
 62         $this->_dom->load($graphmlFile);
 63 
 64         $this->_xp = new DOMXPath($this->_dom);
 65         $this->_xp->registerNamespace('ns','http://graphml.graphdrawing.org/xmlns');
 66         $this->_xp->registerNamespace('y','http://www.yworks.com/xml/graphml');
 67 
 68         $this->_extractYedProperties();
 69 
 70         $workflow = $this->_collectWorkflow();
 71         //echo '<b>Workflow : </b><pre>'.CVarDumper::dumpAsString($workflow).'</pre><hr/>';
 72 
 73         $nodes = $this->_collectNodes();
 74         //echo '<b>nodes : </b><pre>'.CVarDumper::dumpAsString($nodes).'</pre><hr/>';
 75 
 76         $edges = $this->_collectEdges();
 77         //echo '<b>Edges : </b><pre>'.CVarDumper::dumpAsString($edges).'</pre><hr/>';
 78 
 79 
 80         $result = $this->_createSwWorkflow($workflow, $nodes, $edges);
 81         //echo '<b>Result : </b><pre>'.CVarDumper::dumpAsString($result).'</pre><hr/>';
 82         return $result;
 83 
 84     }
 85     /**
 86      * Merges all arrays extracted from the graphml file (workflow, nodes, edges) to create and
 87      * return a single array descrbing a simpleWorkflow.
 88      *
 89      * @param array() $w workflow attributes
 90      * @param array() $n nodes attributes
 91      * @param array() $e edges attributes
 92      */
 93     private function _createSwWorkflow($w,$n,$e)
 94     {
 95         $nodes=array();
 96 
 97         foreach ($n as $key => $node) {
 98                 
 99             $newNode = array(
100             'id'         => $node['id'],
101             'label'      => $node['label'],
102             'constraint' => $node['constraint'],
103             'metadata'   => array(
104             'background-color' => $node['background-color'],
105             'color'            => $node['color']
106             )
107             );
108             if(isset($e[$key])){
109                 foreach ($e[$key] as $trgKey => $edge) {
110                     $newNode['transition'][$n[$trgKey]['id']] =  $edge;
111                 }
112             }
113                 
114             // normalize transitions
115                 
116             if(isset($newNode['transition'])){
117                 foreach($newNode['transition'] as $targetId => $edge){
118                     if(count($edge) == 0){
119                         unset($newNode['transition'][$targetId]);
120                         $newNode['transition'][] = $targetId;
121                     }elseif(isset($edge['task'])){
122                         $newNode['transition'][$targetId] = $edge['task'];
123                     }
124                 }
125             }
126             $nodes[] = $newNode;
127         }
128         return  array(
129         'initial' => $w['initial'],
130         'node'    => $nodes
131         );
132     }
133     /**
134      * Retrieve the graphml id attribute for each named properties defines in the <em>_mapper</em>
135      * array.
136      */
137     private function _extractYedProperties()
138     {
139         foreach ($this->_mapper as $attrName => $xp) {
140                 
141             $nodeList = $this->_xp->query($xp);
142             if( $nodeList->length != 1){
143                 throw new CException("failed to extract id for attribute $attrName");
144             }
145                 
146             $this->_yedProperties[$attrName] = $nodeList->item(0)->value;
147         }
148     }
149     /**
150      *
151      * @throws CException
152      */
153     private function _collectWorkflow()
154     {
155         $nlGraph = $this->_xp->query('//ns:graph');
156         if($nlGraph->length == 0)
157             throw new CException("no workflow definition found");
158 
159         if($nlGraph->length > 1)
160             throw new CException("more than one workflow found");
161 
162         // extract custom properties /////////////////////////////////////////////////////////////////
163         // INITIAL
164 
165         $nl2 = $this->_xp->query('/ns:graphml/ns:graph/ns:data[@key="'.$this->_yedProperties['w-intial-node-id'].'"]');
166         if($nl2->length!=1 || $this->_isBlank($nl2->item(0)->nodeValue) )
167             throw new CException("failed to extract initial node id for this workflow");
168 
169         $result=array(
170         'initial' => trim($nl2->item(0)->nodeValue)
171         );
172 
173         return $result;
174     }
175     /**
176      * Extract edges defined in the graphml input file
177      * @return array()
178      * @throws CException
179      */
180     private function _collectEdges()
181     {
182         $nlEdges = $this->_xp->query('//ns:edge');
183         if($nlEdges->length == 0)
184             throw new CException("no edge could be found in this workflow");
185 
186         $result=array();
187         for($i=0; $i < $nlEdges->length; $i++)
188         {
189             $currentNode= $nlEdges->item($i);
190 
191             $source = trim($this->_xp->query("@source",$currentNode)->item(0)->value);
192             $target = trim($this->_xp->query("@target",$currentNode)->item(0)->value);
193                 
194             if(!isset($result[$source]) || !isset($result[$source][$target])){
195                 $result[$source][$target] = array();
196             }
197                 
198             // extract custom properties /////////////////////////////////////////////////////////////////
199             // TASK
200                 
201             $nl2 = $this->_xp->query('ns:data[@key="'.$this->_yedProperties['e-task'].'"]',$currentNode);
202             if($nl2->length==1 && ! $this->_isBlank($nl2->item(0)->nodeValue))
203                 $result[$source][$target]['task'] = trim($nl2->item(0)->nodeValue);
204         }
205         return $result;
206     }
207     /**
208      * Extract nodes defined in the graphml input file.<br/>
209      * When working with yEd, remember that the node 'label' is used as the node id by simpleWorkflow. This is the only
210      * required value for a valid node. A node with no label in yEd will be ingored by this converter.
211      *
212      * @return array()
213      * @throws CException
214      */
215     private function _collectNodes()
216     {
217         $nlNodes = $this->_xp->query('//ns:node');
218         if($nlNodes->length == 0)
219             throw new CException("no node could be found in this workflow");
220 
221         $result=array();
222         for($i=0; $i < $nlNodes->length; $i++)
223         {
224             $currentNode = $nlNodes->item($i);
225                 
226             $nl2 = $this->_xp->query("@id",$currentNode);
227                 
228             if($nl2->length != 1)
229                 throw new CException("failed to extract yed node id");
230                 
231             // yEd node Id
232             $yNodeId = trim($nl2->item(0)->value);
233                 
234             // extract mandatory properties ////////////////////////////////////////////////////////////
235                 
236             $nl2 = $this->_xp->query('ns:data[@key="'.$this->_yedProperties['n-graphics'].'"]/y:ShapeNode/y:NodeLabel',$currentNode);
237             if($nl2->length !=1)
238                 continue;
239                 
240             $result[$yNodeId] = array();
241             $result[$yNodeId]['id'] = trim($nl2->item(0)->nodeValue);
242 
243             // extract custom properties /////////////////////////////////////////////////////////////////
244                 
245             $nl2 = $this->_xp->query('ns:data[@key="'.$this->_yedProperties['n-constraint'].'"]',$currentNode);
246             if($nl2->length==1)
247                 $result[$yNodeId]['constraint'] = trim($nl2->item(0)->nodeValue);
248                 
249             $nl2 = $this->_xp->query('ns:data[@key="'.$this->_yedProperties['n-label'].'"]',$currentNode);
250             if($nl2->length==1)
251                 $result[$yNodeId]['label'] = trim($nl2->item(0)->nodeValue);
252                 
253             $nl2 = $this->_xp->query('ns:data[@key="'.$this->_yedProperties['n-graphics'].'"]/y:ShapeNode/y:Fill/@color',$currentNode);
254             if($nl2->length ==1)
255                 $result[$yNodeId]['background-color'] = trim($nl2->item(0)->nodeValue);
256                 
257             $nl2 = $this->_xp->query('ns:data[@key="'.$this->_yedProperties['n-graphics'].'"]/y:ShapeNode/y:NodeLabel/@textColor',$currentNode);
258             if($nl2->length ==1)
259                 $result[$yNodeId]['color'] = trim($nl2->item(0)->nodeValue);
260         }
261         return $result;
262     }
263     /**
264      * @param string $str
265      * @return boolean TRUE if the string passed as argument is null, empty, or made of space character(s)
266      */
267     private function _isBlank($str)
268     {
269         return !isset($str) || strlen(trim($str)) == 0;
270     }
271 }
simpleWorkflow API documentation generated by ApiGen 2.8.0