GraphViz
[ class tree: GraphViz ] [ index: GraphViz ] [ all elements ]

Source for file GraphViz.php

Documentation is available at GraphViz.php

  1. <?php
  2.  
  3. /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
  4.  
  5. /**
  6.  * Image_GraphViz
  7.  *
  8.  * PHP version 4 and 5
  9.  *
  10.  * Copyright (c) 2001-2007, Dr. Volker Göbbels <vmg@arachnion.de> and
  11.  * Sebastian Bergmann <sb@sebastian-bergmann.de>. All rights reserved.
  12.  *
  13.  * LICENSE: This source file is subject to version 3.0 of the PHP license
  14.  * that is available through the world-wide-web at the following URI:
  15.  * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
  16.  * the PHP License and are unable to obtain it through the web, please
  17.  * send a note to license@php.net so we can mail you a copy immediately.
  18.  *
  19.  * @category  Image
  20.  * @package   GraphViz
  21.  * @author    Dr. Volker Göbbels <vmg@arachnion.de>
  22.  * @author    Sebastian Bergmann <sb@sebastian-bergmann.de>
  23.  * @author    Karsten Dambekalns <k.dambekalns@fishfarm.de>
  24.  * @author    Michael Lively Jr. <mlively@ft11.net>
  25.  * @author    Philippe Jausions <Philippe.Jausions@11abacus.com>
  26.  * @copyright 2001-2007 Dr. Volker Göbbels <vmg@arachnion.de> and Sebastian Bergmann <sb@sebastian-bergmann.de>
  27.  * @license   http://www.php.net/license/3_0.txt  PHP License 3.0
  28.  * @version   CVS: $Id: GraphViz.php,v 1.29 2008/02/05 19:39:35 jausions Exp $
  29.  * @link      http://pear.php.net/package/Image_GraphViz
  30.  * @link      http://www.graphviz.org/
  31.  * @since     File available since Release 0.1.0
  32.  */
  33.  
  34. /**
  35.  * Required PEAR classes
  36.  */
  37. require_once 'System.php';
  38.  
  39. /**
  40.  * Interface to AT&T's GraphViz tools.
  41.  *
  42.  * The GraphViz class allows for the creation of and to work with directed
  43.  * and undirected graphs and their visualization with AT&T's GraphViz tools.
  44.  *
  45.  * <code>
  46.  * <?php
  47.  * require_once 'Image/GraphViz.php';
  48.  *
  49.  * $graph = new Image_GraphViz();
  50.  *
  51.  * $graph->addNode(
  52.  *   'Node1',
  53.  *   array(
  54.  *     'URL'   => 'http://link1',
  55.  *     'label' => 'This is a label',
  56.  *     'shape' => 'box'
  57.  *   )
  58.  * );
  59.  *
  60.  * $graph->addNode(
  61.  *   'Node2',
  62.  *   array(
  63.  *     'URL'      => 'http://link2',
  64.  *     'fontsize' => '14'
  65.  *   )
  66.  * );
  67.  *
  68.  * $graph->addNode(
  69.  *   'Node3',
  70.  *   array(
  71.  *     'URL'      => 'http://link3',
  72.  *     'fontsize' => '20'
  73.  *   )
  74.  * );
  75.  *
  76.  * $graph->addEdge(
  77.  *   array(
  78.  *     'Node1' => 'Node2'
  79.  *   ),
  80.  *   array(
  81.  *     'label' => 'Edge Label'
  82.  *   )
  83.  * );
  84.  *
  85.  * $graph->addEdge(
  86.  *   array(
  87.  *     'Node1' => 'Node2'
  88.  *   ),
  89.  *   array(
  90.  *     'color' => 'red'
  91.  *   )
  92.  * );
  93.  *
  94.  * $graph->image();
  95.  * ?>
  96.  * </code>
  97.  *
  98.  * @category  Image
  99.  * @package   GraphViz
  100.  * @author    Sebastian Bergmann <sb@sebastian-bergmann.de>
  101.  * @author    Dr. Volker Göbbels <vmg@arachnion.de>
  102.  * @author    Karsten Dambekalns <k.dambekalns@fishfarm.de>
  103.  * @author    Michael Lively Jr. <mlively@ft11.net>
  104.  * @author    Philippe Jausions <Philippe.Jausions@11abacus.com>
  105.  * @copyright 2001-2007 Dr. Volker Göbbels <vmg@arachnion.de> and Sebastian Bergmann <sb@sebastian-bergmann.de>
  106.  * @license   http://www.php.net/license/3_0.txt The PHP License, Version 3.0
  107.  * @version   Release: 1.3.0RC3
  108.  * @link      http://pear.php.net/package/Image_GraphViz
  109.  * @link      http://www.graphviz.org/
  110.  * @since     Class available since Release 0.1
  111.  */
  112. {
  113.     /**
  114.      * Base path to GraphViz commands
  115.      *
  116.      * @var string 
  117.      */
  118.     var $binPath = '';
  119.  
  120.     /**
  121.      * Path to GraphViz/dot command
  122.      *
  123.      * @var string 
  124.      */
  125.     var $dotCommand = 'dot';
  126.  
  127.     /**
  128.      * Path to GraphViz/neato command
  129.      *
  130.      * @var string 
  131.      */
  132.     var $neatoCommand = 'neato';
  133.  
  134.     /**
  135.      * Representation of the graph
  136.      *
  137.      * @var array 
  138.      */
  139.     var $graph = array('edgesFrom'  => array(),
  140.                        'nodes'      => array(),
  141.                        'attributes' => array(),
  142.                        'directed'   => true,
  143.                        'clusters'   => array(),
  144.                        'name'       => 'G',
  145.                        'strict'     => true,
  146.                       );
  147.  
  148.     /**
  149.      * Whether to return PEAR_Error instance on failures instead of FALSE
  150.      *
  151.      * @var boolean 
  152.      * @access protected
  153.      */
  154.     var $_returnFalseOnError = true;
  155.  
  156.     /**
  157.      * Constructor.
  158.      *
  159.      * Setting the name of the Graph is useful for including multiple image
  160.      * maps on one page. If not set, the graph will be named 'G'.
  161.      *
  162.      * @param boolean $directed    Directed (TRUE) or undirected (FALSE) graph.
  163.      *                              Note: You MUST pass a boolean, and not just
  164.      *                              an  expression that evaluates to TRUE or
  165.      *                              FALSE (i.e. NULL, empty string, 0 will NOT
  166.      *                              work)
  167.      * @param array   $attributes  Attributes of the graph
  168.      * @param string  $name        Name of the Graph
  169.      * @param boolean $strict      Whether to collapse multiple edges between
  170.      *                              same nodes
  171.      * @param boolean $returnError Set to TRUE to return PEAR_Error instances
  172.      *                              on failures instead of FALSE
  173.      *
  174.      * @access public
  175.      */
  176.     function Image_GraphViz($directed = true$attributes = array(),
  177.                             $name 'G'$strict = true$returnError = false)
  178.     {
  179.         $this->setDirected($directed);
  180.         $this->setAttributes($attributes);
  181.         $this->graph['name']   $name;
  182.         $this->graph['strict'= (boolean)$strict;
  183.  
  184.         $this->_returnFalseOnError = !$returnError;
  185.     }
  186.  
  187.     /**
  188.      * Outputs image of the graph in a given format
  189.      *
  190.      * This methods send HTTP headers
  191.      *
  192.      * @param string $format  Format of the output image. This may be one
  193.      *                         of the formats supported by GraphViz.
  194.      * @param string $command "dot" or "neato"
  195.      *
  196.      * @return boolean TRUE on success, FALSE or PEAR_Error otherwise
  197.      * @access public
  198.      */
  199.     function image($format 'svg'$command = null)
  200.     {
  201.         $file $this->saveParsedGraph();
  202.         if (!$file || PEAR::isError($file)) {
  203.             return $file;
  204.         }
  205.  
  206.         $outputfile $file '.' $format;
  207.  
  208.         $rendered $this->renderDotFile($file$outputfile$format,
  209.                                          $command);
  210.         if ($rendered !== true{
  211.             return $rendered;
  212.         }
  213.  
  214.         $sendContentLengthHeader = true;
  215.  
  216.         switch (strtolower($format)) {
  217.         case 'gif':
  218.         case 'png':
  219.         case 'bmp':
  220.         case 'jpeg':
  221.         case 'tiff':
  222.             header('Content-Type: image/' $format);
  223.             break;
  224.  
  225.         case 'tif':
  226.             header('Content-Type: image/tiff');
  227.             break;
  228.  
  229.         case 'jpg':
  230.             header('Content-Type: image/jpeg');
  231.             break;
  232.  
  233.         case 'ico':
  234.             header('Content-Type: image/x-icon');
  235.             break;
  236.  
  237.         case 'wbmp':
  238.             header('Content-Type: image/vnd.wap.wbmp');
  239.             break;
  240.  
  241.         case 'pdf':
  242.             header('Content-Type: application/pdf');
  243.             break;
  244.  
  245.         case 'mif':
  246.             header('Content-Type: application/vnd.mif');
  247.             break;
  248.  
  249.         case 'vrml':
  250.             header('Content-Type: application/x-vrml');
  251.             break;
  252.  
  253.         case 'svg':
  254.             header('Content-Type: image/svg+xml');
  255.             break;
  256.  
  257.         case 'plain':
  258.         case 'plain-ext':
  259.             header('Content-Type: text/plain');
  260.             break;
  261.  
  262.         default:
  263.             header('Content-Type: application/octet-stream');
  264.             $sendContentLengthHeader = false;
  265.         }
  266.  
  267.         if ($sendContentLengthHeader{
  268.             header('Content-Length: ' filesize($outputfile));
  269.         }
  270.  
  271.         $return = true;
  272.         if (readfile($outputfile=== false{
  273.             $return = false;
  274.         }
  275.         @unlink($outputfile);
  276.  
  277.         return $return;
  278.     }
  279.  
  280.     /**
  281.      * Returns image (data) of the graph in a given format.
  282.      *
  283.      * @param string $format  Format of the output image. This may be one
  284.      *                         of the formats supported by GraphViz.
  285.      * @param string $command "dot" or "neato"
  286.      *
  287.      * @return string The image (data) created by GraphViz, FALSE or PEAR_Error
  288.      *                 on error
  289.      * @access public
  290.      * @since  Method available since Release 1.1.0
  291.      */
  292.     function fetch($format 'svg'$command = null)
  293.     {
  294.         $file $this->saveParsedGraph();
  295.         if (!$file || PEAR::isError($file)) {
  296.             return $file;
  297.         }
  298.  
  299.         $outputfile $file '.' $format;
  300.  
  301.         $rendered $this->renderDotFile($file$outputfile$format,
  302.                                          $command);
  303.         if ($rendered !== true{
  304.             return $rendered;
  305.         }
  306.  
  307.         @unlink($file);
  308.  
  309.         $fp fopen($outputfile'rb');
  310.  
  311.         if (!$fp{
  312.             if ($this->_returnFalseOnError{
  313.                 return false;
  314.             }
  315.             $error = PEAR::raiseError('Could not read rendered file');
  316.             return $error;
  317.         }
  318.  
  319.         $data fread($fpfilesize($outputfile));
  320.         fclose($fp);
  321.         @unlink($outputfile);
  322.  
  323.         return $data;
  324.     }
  325.  
  326.     /**
  327.      * Renders a given dot file into a given format.
  328.      *
  329.      * @param string $dotfile    The absolute path of the dot file to use.
  330.      * @param string $outputfile The absolute path of the file to save to.
  331.      * @param string $format     Format of the output image. This may be one
  332.      *                            of the formats supported by GraphViz.
  333.      * @param string $command    "dot" or "neato"
  334.      *
  335.      * @return boolean TRUE if the file was saved, FALSE or PEAR_Error
  336.      *                  otherwise.
  337.      * @access public
  338.      */
  339.     function renderDotFile($dotfile$outputfile$format 'svg',
  340.                            $command = null)
  341.     {
  342.         if (!file_exists($dotfile)) {
  343.             if ($this->_returnFalseOnError{
  344.                 return false;
  345.             }
  346.             $error = PEAR::raiseError('Could not find dot file');
  347.             return $error;
  348.         }
  349.  
  350.         $oldmtime file_exists($outputfilefilemtime($outputfile: 0;
  351.  
  352.         switch ($command{
  353.         case 'dot':
  354.         case 'neato':
  355.             break;
  356.         default:
  357.             $command $this->graph['directed''dot' 'neato';
  358.         }
  359.         $command_orig $command;
  360.  
  361.         $command $this->binPath.(($command == 'dot'$this->dotCommand
  362.                                                        : $this->neatoCommand);
  363.  
  364.         $command .= ' -T'.escapeshellarg($format)
  365.                     .' -o'.escapeshellarg($outputfile)
  366.                     .' '.escapeshellarg($dotfile)
  367.                     .' 2>&1';
  368.         exec($command$msg$return_val);
  369.  
  370.         clearstatcache();
  371.         if (file_exists($outputfile&& filemtime($outputfile$oldmtime
  372.             && $return_val == 0{
  373.             return true;
  374.         elseif ($this->_returnFalseOnError{
  375.             return false;
  376.         }
  377.         $error = PEAR::raiseError($command_orig.' command failed: '
  378.                                   .implode("\n"$msg));
  379.         return $error;
  380.     }
  381.  
  382.     /**
  383.      * Adds a cluster to the graph.
  384.      *
  385.      * @param string $id         ID.
  386.      * @param array  $title      Title.
  387.      * @param array  $attributes Attributes of the cluster.
  388.      *
  389.      * @return void 
  390.      * @access public
  391.      */
  392.     function addCluster($id$title$attributes = array())
  393.     {
  394.         $this->graph['clusters'][$id]['title']      $title;
  395.         $this->graph['clusters'][$id]['attributes'$attributes;
  396.     }
  397.  
  398.     /**
  399.      * Adds a note to the graph.
  400.      *
  401.      * @param string $name       Name of the node.
  402.      * @param array  $attributes Attributes of the node.
  403.      * @param string $group      Group of the node.
  404.      *
  405.      * @return void 
  406.      * @access public
  407.      */
  408.     function addNode($name$attributes = array()$group 'default')
  409.     {
  410.         $this->graph['nodes'][$group][$name$attributes;
  411.     }
  412.  
  413.     /**
  414.      * Removes a node from the graph.
  415.      *
  416.      * This method doesn't remove edges associated with the node.
  417.      *
  418.      * @param string $name  Name of the node to be removed.
  419.      * @param string $group Group of the node.
  420.      *
  421.      * @return void 
  422.      * @access public
  423.      */
  424.     function removeNode($name$group 'default')
  425.     {
  426.         if (isset($this->graph['nodes'][$group][$name])) {
  427.             unset($this->graph['nodes'][$group][$name]);
  428.         }
  429.     }
  430.  
  431.     /**
  432.      * Adds an edge to the graph.
  433.      *
  434.      * Examples:
  435.      * <code>
  436.      * $g->addEdge(array('node1' => 'node2'));
  437.      * $attr = array(
  438.      *     'label' => '+1',
  439.      *     'style' => 'dashed',
  440.      * );
  441.      * $g->addEdge(array('node3' => 'node4'), $attr);
  442.      *
  443.      * // With port specification
  444.      * $g->addEdge(array('node5' => 'node6'), $attr, array('node6' => 'portA'));
  445.      * $g->addEdge(array('node7' => 'node8'), null, array('node7' => 'portC',
  446.      *                                                    'node8' => 'portD'));
  447.      * </code>
  448.      *
  449.      * @param array $edge       Start => End node of the edge.
  450.      * @param array $attributes Attributes of the edge.
  451.      * @param array $ports      Start node => port, End node => port
  452.      *
  453.      * @return integer an edge ID that can be used with {@link removeEdge()}
  454.      * @access public
  455.      */
  456.     function addEdge($edge$attributes = array()$ports = array())
  457.     {
  458.         if (!is_array($edge)) {
  459.             return;
  460.         }
  461.  
  462.         $from key($edge);
  463.         $to   $edge[$from];
  464.         $info = array();
  465.  
  466.         if (is_array($ports)) {
  467.             if (array_key_exists($from$ports)) {
  468.                 $info['portFrom'$ports[$from];
  469.             }
  470.  
  471.             if (array_key_exists($to$ports)) {
  472.                 $info['portTo'$ports[$to];
  473.             }
  474.         }
  475.  
  476.         if (is_array($attributes)) {
  477.             $info['attributes'$attributes;
  478.         }
  479.  
  480.         if (!empty($this->graph['strict'])) {
  481.             if (!isset($this->graph['edgesFrom'][$from][$to][0])) {
  482.                 $this->graph['edgesFrom'][$from][$to][0$info;
  483.             else {
  484.                 $this->graph['edgesFrom'][$from][$to][0array_merge($this->graph['edgesFrom'][$from][$to][0]$info);
  485.             }
  486.         else {
  487.             $this->graph['edgesFrom'][$from][$to][$info;
  488.         }
  489.  
  490.         return count($this->graph['edgesFrom'][$from][$to]- 1;
  491.     }
  492.  
  493.     /**
  494.      * Removes an edge from the graph.
  495.      *
  496.      * @param array   $edge Start and End node of the edge to be removed.
  497.      * @param integer $id   specific edge ID (only usefull when multiple edges
  498.      *                       exist between the same 2 nodes)
  499.      *
  500.      * @return void 
  501.      * @access public
  502.      */
  503.     function removeEdge($edge$id = null)
  504.     {
  505.         if (!is_array($edge)) {
  506.             return;
  507.         }
  508.  
  509.         $from key($edge);
  510.         $to   $edge[$from];
  511.  
  512.         if (!is_null($id)) {
  513.             if (isset($this->graph['edgesFrom'][$from][$to][$id])) {
  514.                 unset($this->graph['edgesFrom'][$from][$to][$id]);
  515.  
  516.                 if (count($this->graph['edgesFrom'][$from][$to]== 0{
  517.                     unset($this->graph['edgesFrom'][$from][$to]);
  518.                 }
  519.             }
  520.         elseif (isset($this->graph['edgesFrom'][$from][$to])) {
  521.             unset($this->graph['edgesFrom'][$from][$to]);
  522.         }
  523.     }
  524.  
  525.     /**
  526.      * Adds attributes to the graph.
  527.      *
  528.      * @param array $attributes Attributes to be added to the graph.
  529.      *
  530.      * @return void 
  531.      * @access public
  532.      */
  533.     function addAttributes($attributes)
  534.     {
  535.         if (is_array($attributes)) {
  536.             $this->graph['attributes'array_merge($this->graph['attributes']$attributes);
  537.         }
  538.     }
  539.  
  540.     /**
  541.      * Sets attributes of the graph.
  542.      *
  543.      * @param array $attributes Attributes to be set for the graph.
  544.      *
  545.      * @return void 
  546.      * @access public
  547.      */
  548.     function setAttributes($attributes)
  549.     {
  550.         if (is_array($attributes)) {
  551.             $this->graph['attributes'$attributes;
  552.         }
  553.     }
  554.  
  555.     /**
  556.      * Escapes an (attribute) array
  557.      *
  558.      * Detects if an attribute is <html>, contains double-quotes, etc...
  559.      *
  560.      * @param array $input input to escape
  561.      *
  562.      * @return array input escaped
  563.      * @access protected
  564.      */
  565.     function _escapeArray($input)
  566.     {
  567.         $output = array();
  568.  
  569.         foreach ((array)$input as $k => $v{
  570.             switch ($k{
  571.             case 'label':
  572.             case 'headlabel':
  573.             case 'taillabel':
  574.                 $v $this->_escape($vtrue);
  575.                 break;
  576.             default:
  577.                 $v $this->_escape($v);
  578.                 $k $this->_escape($k);
  579.             }
  580.  
  581.             $output[$k$v;
  582.         }
  583.  
  584.         return $output;
  585.     }
  586.  
  587.     /**
  588.      * Returns a safe "ID" in DOT syntax
  589.      *
  590.      * @param string  $input string to use as "ID"
  591.      * @param boolean $html  whether to attempt detecting HTML-like content
  592.      *
  593.      * @return string 
  594.      * @access protected
  595.      */
  596.     function _escape($input$html = false)
  597.     {
  598.         switch (strtolower($input)) {
  599.         case 'node':
  600.         case 'edge':
  601.         case 'graph':
  602.         case 'digraph':
  603.         case 'subgraph':
  604.         case 'strict':
  605.             return '"'.$input.'"';
  606.         }
  607.  
  608.         if (is_bool($input)) {
  609.             return ($input'true' 'false';
  610.         }
  611.  
  612.         if ($html && (strpos($input'</'!== false
  613.                       || strpos($input'/>'!== false)) {
  614.             return '<'.$input.'>';
  615.         }
  616.  
  617.         if (preg_match('/^([a-z_][a-z_0-9]*|-?(\.[0-9]+|[0-9]+(\.[0-9]*)?))$/i',
  618.                        $input)) {
  619.             return $input;
  620.         }
  621.  
  622.         return '"'.str_replace(array("\r\n""\n""\r"'"'),
  623.                                array('\n',   '\n''\n''\"')$input).'"';
  624.     }
  625.  
  626.     /**
  627.      * Sets directed/undirected flag for the graph.
  628.      *
  629.      * Note: You MUST pass a boolean, and not just an expression that evaluates
  630.      *       to TRUE or FALSE (i.e. NULL, empty string, 0 will not work)
  631.      *
  632.      * @param boolean $directed Directed (TRUE) or undirected (FALSE) graph.
  633.      *
  634.      * @return void 
  635.      * @access public
  636.      */
  637.     function setDirected($directed)
  638.     {
  639.         if (is_bool($directed)) {
  640.             $this->graph['directed'$directed;
  641.         }
  642.     }
  643.  
  644.     /**
  645.      * Loads a graph from a file in Image_GraphViz format
  646.      *
  647.      * @param string $file File to load graph from.
  648.      *
  649.      * @return void 
  650.      * @access public
  651.      */
  652.     function load($file)
  653.     {
  654.         if ($serializedGraph implode(''@file($file))) {
  655.             $g unserialize($serializedGraph);
  656.  
  657.             if (!is_array($g)) {
  658.                 return;
  659.             }
  660.  
  661.             // Convert old storage format to new one
  662.             $defaults = array('edgesFrom'  => array(),
  663.                               'nodes'      => array(),
  664.                               'attributes' => array(),
  665.                               'directed'   => true,
  666.                               'clusters'   => array(),
  667.                               'name'       => 'G',
  668.                               'strict'     => true,
  669.                         );
  670.  
  671.             $this->graph = array_merge($defaults$g);
  672.  
  673.             if (isset($this->graph['edges'])) {
  674.                 foreach ($this->graph['edges'as $id => $nodes{
  675.                     $attr (isset($this->graph['edgeAttributes'][$id]))
  676.                             ? $this->graph['edgeAttributes'][$id]
  677.                             : array();
  678.  
  679.                     $this->addEdge($nodes$attr);
  680.                 }
  681.  
  682.                 unset($this->graph['edges']);
  683.                 unset($this->graph['edgeAttributes']);
  684.             }
  685.         }
  686.     }
  687.  
  688.     /**
  689.      * Save graph to file in Image_GraphViz format
  690.      *
  691.      * This saves the serialized version of the instance, not the
  692.      * rendered graph.
  693.      *
  694.      * @param string $file File to save the graph to.
  695.      *
  696.      * @return string File the graph was saved to, FALSE or PEAR_Error on
  697.      *                 failure.
  698.      * @access public
  699.      */
  700.     function save($file '')
  701.     {
  702.         $serializedGraph serialize($this->graph);
  703.  
  704.         if (empty($file)) {
  705.             $file = System::mktemp('graph_');
  706.         }
  707.  
  708.         if ($fp @fopen($file'wb')) {
  709.             @fputs($fp$serializedGraph);
  710.             @fclose($fp);
  711.  
  712.             return $file;
  713.         }
  714.  
  715.         if ($this->_returnFalseOnError{
  716.             return false;
  717.         }
  718.         $error = PEAR::raiseError('Could not save serialized graph instance');
  719.         return $error;
  720.     }
  721.  
  722.     /**
  723.      * Parses the graph into GraphViz markup.
  724.      *
  725.      * @return string GraphViz markup
  726.      * @access public
  727.      */
  728.     function parse()
  729.     {
  730.         $parsedGraph  (empty($this->graph['strict'])) '' 'strict ';
  731.         $parsedGraph .= (empty($this->graph['directed'])) 'graph ' 'digraph ';
  732.         $parsedGraph .= $this->_escape($this->graph['name'])." {\n";
  733.  
  734.         $indent '    ';
  735.  
  736.         $attr $this->_escapeArray($this->graph['attributes']);
  737.  
  738.         foreach ($attr as $key => $value{
  739.             $parsedGraph .= $indent.$key.'='.$value.";\n";
  740.         }
  741.  
  742.         foreach ($this->graph['nodes'as $group => $nodes{
  743.             if ($group != 'default'{
  744.                 $parsedGraph .= $indent.'subgraph '.$this->_escape($group)." {\n";
  745.  
  746.                 $indent .= '    ';
  747.  
  748.                 if (isset($this->graph['clusters'][$group])) {
  749.                     $cluster $this->graph['clusters'][$group];
  750.                     $attr    $this->_escapeArray($cluster['attributes']);
  751.  
  752.                     foreach ($attr as $key => $value{
  753.                         $attr[$key.'='.$value;
  754.                     }
  755.  
  756.                     if (strlen($cluster['title'])) {
  757.                         $attr['label='
  758.                                   .$this->_escape($cluster['title']true);
  759.                     }
  760.  
  761.                     if ($attr{
  762.                         $parsedGraph .= $indent.'graph [ '.implode(','$attr)
  763.                                         ." ];\n";
  764.                     }
  765.                 }
  766.             }
  767.  
  768.             foreach ($nodes as $node => $attributes{
  769.                 $parsedGraph .= $indent.$this->_escape($node);
  770.  
  771.                 $attributeList = array();
  772.  
  773.                 foreach ($this->_escapeArray($attributesas $key => $value{
  774.                     $attributeList[$key.'='.$value;
  775.                 }
  776.  
  777.                 if (!empty($attributeList)) {
  778.                     $parsedGraph .= ' [ '.implode(','$attributeList).' ]';
  779.                 }
  780.  
  781.                 $parsedGraph .= ";\n";
  782.             }
  783.  
  784.             if ($group != 'default'{
  785.                 $indent substr($indent0-4);
  786.  
  787.                 $parsedGraph .= $indent."}\n";
  788.             }
  789.         }
  790.  
  791.         if (!empty($this->graph['directed'])) {
  792.             $separator ' -> ';
  793.         else {
  794.             $separator ' -- ';
  795.         }
  796.  
  797.         foreach ($this->graph['edgesFrom'as $from => $toNodes{
  798.             $from $this->_escape($from);
  799.  
  800.             foreach ($toNodes as $to => $edges{
  801.                 $to $this->_escape($to);
  802.  
  803.                 foreach ($edges as $info{
  804.                     $f $from;
  805.                     $t $to;
  806.  
  807.                     if (array_key_exists('portFrom'$info)) {
  808.                         $f .= ':'.$this->_escape($info['portFrom']);
  809.                     }
  810.  
  811.                     if (array_key_exists('portTo'$info)) {
  812.                         $t .= ':'.$this->_escape($info['portTo']);
  813.                     }
  814.  
  815.                     $parsedGraph .= $indent.$f.$separator.$t;
  816.  
  817.                     if (!empty($info['attributes'])) {
  818.                         $attributeList = array();
  819.  
  820.                         foreach ($this->_escapeArray($info['attributes']as $key => $value{
  821.                             $attributeList[$key.'='.$value;
  822.                         }
  823.  
  824.                         $parsedGraph .= ' [ '.implode(','$attributeList).' ]';
  825.                     }
  826.  
  827.                     $parsedGraph .= ";\n";
  828.                 }
  829.             }
  830.         }
  831.  
  832.         return $parsedGraph "}\n";
  833.     }
  834.  
  835.     /**
  836.      * Saves GraphViz markup to file (in DOT language)
  837.      *
  838.      * @param string $file File to write the GraphViz markup to.
  839.      *
  840.      * @return string File to which the GraphViz markup was written, FALSE or
  841.      *                 or PEAR_Error on failure.
  842.      * @access public
  843.      */
  844.     function saveParsedGraph($file '')
  845.     {
  846.         $parsedGraph $this->parse();
  847.  
  848.         if (!empty($parsedGraph)) {
  849.             if (empty($file)) {
  850.                 $file = System::mktemp('graph_');
  851.             }
  852.  
  853.             if ($fp @fopen($file'wb')) {
  854.                 @fputs($fp$parsedGraph);
  855.                 @fclose($fp);
  856.  
  857.                 return $file;
  858.             }
  859.         }
  860.  
  861.         if ($this->_returnFalseOnError{
  862.             return false;
  863.         }
  864.         $error = PEAR::raiseError('Could not save graph');
  865.         return $error;
  866.     }
  867. }
  868.  
  869. /*
  870.  * Local variables:
  871.  * tab-width: 4
  872.  * c-basic-offset: 4
  873.  * c-hanging-comment-ender-p: nil
  874.  * End:
  875.  */
  876. ?>

Documentation generated on Tue, 05 Feb 2008 15:00:05 -0500 by phpDocumentor 1.4.0. PEAR Logo Copyright © PHP Group 2004.