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 SWyEdConverter
 24 {
 25     /**
 26      * @var array mapper between SW Workflow attributes and yEd attributes
 27      */
 28     public $mapper = array(
 29         'workflow' => array(
 30             'id'      => 'Id',                  // optional
 31             'initial' => 'Initial-node-id',     // mandatory
 32         ),
 33         'node' => array(
 34             'id'                => 'xpath|y:ShapeNode/y:NodeLabel',                 // mandatory
 35             'constraint'        => 'Constraint',                                    // optional
 36             'label'             => 'Label',                                         // optional
 37             'metadata.color'    => 'xpath|y:ShapeNode/y:NodeLabel/@textColor',      // optional
 38             'metadata.bgcolor'  => 'xpath|y:ShapeNode/y:Fill/@color',               // optional
 39         ),
 40         'edge' => array(
 41             'task' => 'Task'                                                        // optional
 42         )
 43     );
 44     
 45     /////////////////////////////////////////////////////////////////////////////////////////////////////
 46     // private members
 47     
 48     private $_xml;
 49     private $_yedProperties;
 50     private $_workflow = array(
 51         'workflow' => array(),
 52         'node'     => array()
 53     );
 54     /**
 55      *
 56      * @param string $file name of th graphml (xml) file to process
 57      * @return array the workflow definition
 58      */
 59     public function convert($file)
 60     {
 61         if(!extension_loaded('domxml')){
 62             throw new SWException('extension domxml not loaded : yEd converter requires domxml extension to process');
 63         }
 64         // reset all
 65         $this->_xml           = null;
 66         $this->_yedProperties = array();
 67         $this->_workflow      = array(
 68             'workflow' => array(),
 69             'node'     => array()
 70         );
 71                 
 72         $this->_xml = simplexml_load_file($file);
 73         $namespaces = $this->_xml->getNamespaces(true);
 74         
 75         $this->_xml->registerXPathNamespace('y', 'http://www.yworks.com/xml/graphml');
 76         $this->_xml->registerXPathNamespace('__empty_ns', $namespaces['']);
 77         
 78         // extract yEd custom attributes IDs ///////////////////////////////////////////////////////////////////
 79         //
 80         // for instance : <key attr.name="description" attr.type="string" for="node" id="d7"/>
 81         // is turned into
 82         // array(
 83         //  'node' => array(
 84         //      'description' => 'd7',
 85         //      'nodegraphics' => 'd9',
 86         //      etc...
 87         //  )
 88         //
 89         
 90         $nlKey = $this->_xml->xpath('/__empty_ns:graphml/__empty_ns:key');
 91 
 92         foreach($nlKey as $ndKey){
 93         
 94             if( $ndKey['attr.name'] != null &&
 95                 ( $ndKey['for'] == 'node' ||  $ndKey['for'] == 'edge' || $ndKey['for'] == 'graph'))
 96             {
 97                 $for      = (string) $ndKey['for'];
 98                 $attrName = (string) $ndKey['attr.name'];
 99         
100                 if( ! isset($this->_yedProperties[$for]) )  $this->_yedProperties[$for] = array();
101         
102                 $this->_yedProperties[$for][$attrName] = (string) $ndKey['id'];
103             }
104             elseif(  $ndKey['yfiles.type'] == 'nodegraphics' ||  $ndKey['yfiles.type'] == 'edgegraphics' )
105             {
106                 $for      = (string )$ndKey['for'];
107                 $fileType = (string) $ndKey['yfiles.type'];
108                 
109                 $this->_yedProperties[$for][$fileType] = (string) $ndKey['id'];
110             }
111         }
112         $nodeGraphicId = $this->_yedProperties['node']['nodegraphics'];
113         
114         // extract workflow properties /////////////////////////////////////////////////////
115 
116         $nlGraph    = $this->_xml->xpath('//__empty_ns:graph');
117         $yedGraphId = (string) $nlGraph[0]['id'];
118         
119         foreach($this->mapper['workflow'] as $swAttrName => $yedAttrName)
120         {
121             // echo '<li>Extracting attribute sw:'.$swAttrName.' from yed:'.$yedAttrName.'</li>';
122         
123             // creates the XPath that is applied to the yEd XML file in order to retrieve
124             // value for simpleWorkflow attribute $swAttrName (and for the current workflow)
125         
126             if( preg_match('/^xpath\|(.*)$/',$yedAttrName,$matches))
127             {
128                 $xpath = '//__empty_ns:graph[@id="'.$yedGraphId.'"]/__empty_ns:data[@key="'.$nodeGraphicId.'"]/'.$matches[1];
129             }
130             elseif( isset( $this->_yedProperties['graph'][$yedAttrName]))
131             {
132                 $yedDataKey =  $this->_yedProperties['graph'][$yedAttrName];
133                 $xpath = '//__empty_ns:graph[@id="'.$yedGraphId.'"]/__empty_ns:data[@key="'.$yedDataKey.'"]';
134             }
135             else{
136                 continue;
137             }
138         
139             // // echo '<li>evaluating xpath = '.$xpath.' : ';
140             $result = $this->_xml->xpath($xpath);
141             if( count($result) == 1 ){
142                 // // echo 'value found = <u>'.$result[0].'</u>';
143                 $this->_workflow['workflow'][$swAttrName] = trim((string) $result[0]);
144             }
145             else {
146                 //$this->_workflow['workflow'][$swAttrName] = null;
147                 // // echo 'WARNING : value not found';
148             }
149             // // echo '</li>';
150         }
151         // // echo '</ul>';
152         // // echo '<pre>'.CVarDumper::dumpAsString($this->_workflow['workflow']).'</pre>';
153 
154         // extract nodes ///////////////////////////////////////////////////////////////////
155         
156         // // echo '<h2>Extracting Nodes</h2>';
157         
158         $nlNode = $this->_xml->xpath('//__empty_ns:node');
159         foreach($nlNode as $ndNode)
160         {
161             $yNodeId = (string) $ndNode['id'];
162             $this->_workflow['node'][$yNodeId] = array();
163             // // echo 'yEd node Id = '.$yNodeId.'<br/>';
164         
165             foreach($this->mapper['node'] as $swAttrName => $yedAttrName)
166             {
167         
168                 // // echo 'maping (sw)'.$swAttrName.' &lt;- (yEd)'.$yedAttrName.'<br/>';
169         
170                 // creates the XPath that is applied to the yEd XML file in order to retrieve
171                 // value for simpleWorkflow attribute $swAttrName (and for the current node)
172         
173                 if( preg_match('/^xpath\|(.*)$/',$yedAttrName,$matches))
174                 {
175                     $relXpath = $matches[1];
176                     $xpath = '//__empty_ns:node[@id="'.$yNodeId.'"]/__empty_ns:data[@key="'.$nodeGraphicId.'"]/'.$relXpath;
177                 }
178                 elseif( isset( $this->_yedProperties['node'][$yedAttrName]))
179                 {
180                     $yedDataKey =  $this->_yedProperties['node'][$yedAttrName];
181                     $xpath = '//__empty_ns:node[@id="'.$yNodeId.'"]/__empty_ns:data[@key="'.$yedDataKey.'"]';
182                 }else{
183                     continue;
184                 }
185         
186                 // XPath could be created : evaluate it now and store the returned value as a string
187         
188                 // echo 'evaluating xpath = '.$xpath.'<br/>';
189                 $result = $this->_xml->xpath($xpath);
190                 if( count($result) == 1 ){
191                     // echo 'value found = '.$result[0].'<br/>';
192                     $this->_workflow['node'][$yNodeId][$swAttrName] = trim((string) $result[0]);
193                 }
194                 else {
195                     //$this->_workflow['node'][$yNodeId][$swAttrName] = null;
196                     // echo 'WARNING : value not found<br/>';
197                 }
198             }
199             // echo '<pre>'.CVarDumper::dumpAsString($this->_workflow['node'][$yNodeId]).'</pre>';
200             // echo '<hr/>';
201         }
202 
203         // process edges ///////////////////////////////////////////////////////////////////
204         
205         // echo '<h2>Extracting Edges</h2>';
206         
207         $nlEdge = $this->_xml->xpath('//__empty_ns:edge');
208         
209         foreach($nlEdge as $ndEdge)
210         {
211             $yedSource = (string) $ndEdge['source'];
212             $yedTarget = (string) $ndEdge['target'];
213             $yEdgeId   = (string) $ndEdge['id'];
214         
215             // echo 'processing edge from '.$yedSource.' to '.$yedTarget.'<br/>';
216         
217             if( isset($this->_workflow['node'][$yedSource]) && isset($this->_workflow['node'][$yedTarget]))
218             {
219                 // echo 'sw nodes found<br/>';
220                 $swNodeSource = & $this->_workflow['node'][$yedSource];
221                 $swNodeTarget = & $this->_workflow['node'][$yedTarget];
222         
223                 if(!isset($swNodeSource['transition'])){
224                     $swNodeSource['transition'] = array();
225                 }
226         
227                 // is there a task for this transition ? Task is the only attribute
228                 // that can be attached to a transition
229         
230                 $yedAttrName = $this->mapper['edge']['task'];
231         
232                 if( preg_match('/^xpath\|(.*)$/',$yedAttrName,$matches))
233                 {
234                     $relXpath = $matches[1];
235                     $xpath = '//__empty_ns:edge[@id="'.$yEdgeId.'"]/__empty_ns:data[@key="'.$nodeGraphicId.'"]/'.$relXpath;
236                 }
237                 elseif( isset( $this->_yedProperties['edge'][$yedAttrName]))
238                 {
239                     $yedDataKey =  $this->_yedProperties['edge'][$yedAttrName];
240                     $xpath = '//__empty_ns:edge[@id="'.$yEdgeId.'"]/__empty_ns:data[@key="'.$yedDataKey.'"]';
241                 }else{
242                     continue;
243                 }
244         
245                 // XPath could be created : evaluate it now and store the returned value as a string
246         
247                 // echo 'evaluating xpath = '.$xpath.'<br/>';
248                 $result = $this->_xml->xpath($xpath);
249                 if( count($result) == 1 ){
250                     // echo 'value found = '.$result[0].'<br/>';
251                     $swNodeSource['transition'][$swNodeTarget['id']] = trim((string) $result[0]);
252                 }
253                 else {
254                     $task = null;
255                     // echo 'WARNING : value not found<br/>';
256                     $swNodeSource['transition'][] = $swNodeTarget['id'];
257                 }
258             }
259         }
260         
261         // echo '<h2>Raw Workflow Array</h2>';
262         // echo '<pre>'.CVarDumper::dumpAsString($this->_workflow).'</pre>';
263         
264         // normalize workflow array ///////////////////////////////////////////////////////////////////
265         
266         // echo '<h2>Normalize Workflow Array</h2>';
267         
268         if(isset($this->_workflow['workflow']['initial'])){
269             $this->_workflow['initial'] = $this->_workflow['workflow']['initial'];
270         }
271 
272         unset($this->_workflow['workflow']);
273         
274         foreach ($this->_workflow['node'] as $key => $node)
275         {
276             $normalizedNode = array();
277             foreach ($node as $nodeAttrName => $nodeAttrValue)
278             {
279                 if( preg_match('/^metadata\.(.*)$/',$nodeAttrName,$matches))
280                 {
281                     if(!isset($normalizedNode['metadata'])){
282                         $normalizedNode['metadata'] = array();
283                     }
284                     // echo 'metadata : '.$mdAttr.' &larr; '.$nodeAttrValue.'<br/>';
285                     $normalizedNode['metadata'][$matches[1]] = $nodeAttrValue;
286                 }else {
287                     $normalizedNode[$nodeAttrName] = $nodeAttrValue;
288                 }
289             }
290             unset($this->_workflow['node'][$key]);
291             $this->_workflow['node'][] = $normalizedNode;
292         }
293         return $this->_workflow;
294     }
295 }
simpleWorkflow API documentation generated by ApiGen 2.8.0