summaryrefslogtreecommitdiff
path: root/includes/pear/Image/GraphViz.php
diff options
context:
space:
mode:
Diffstat (limited to 'includes/pear/Image/GraphViz.php')
-rw-r--r--includes/pear/Image/GraphViz.php1005
1 files changed, 1005 insertions, 0 deletions
diff --git a/includes/pear/Image/GraphViz.php b/includes/pear/Image/GraphViz.php
new file mode 100644
index 0000000..d7de7c5
--- /dev/null
+++ b/includes/pear/Image/GraphViz.php
@@ -0,0 +1,1005 @@
+<?php
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * Image_GraphViz
+ *
+ * PHP version 4 and 5
+ *
+ * Copyright (c) 2001-2007, Dr. Volker Göbbels <vmg@arachnion.de> and
+ * Sebastian Bergmann <sb@sebastian-bergmann.de>. All rights reserved.
+ *
+ * LICENSE: This source file is subject to version 3.0 of the PHP license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.php.net/license/3_0.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category Image
+ * @package Image_GraphViz
+ * @author Dr. Volker Göbbels <vmg@arachnion.de>
+ * @author Sebastian Bergmann <sb@sebastian-bergmann.de>
+ * @author Karsten Dambekalns <k.dambekalns@fishfarm.de>
+ * @author Michael Lively Jr. <mlively@ft11.net>
+ * @author Philippe Jausions <Philippe.Jausions@11abacus.com>
+ * @copyright 2001-2007 Dr. Volker Göbbels <vmg@arachnion.de> and Sebastian Bergmann <sb@sebastian-bergmann.de>
+ * @license http://www.php.net/license/3_0.txt PHP License 3.0
+ * @version CVS: $Id: GraphViz.php 304688 2010-10-24 05:21:17Z clockwerx $
+ * @link http://pear.php.net/package/Image_GraphViz
+ * @link http://www.graphviz.org/
+ * @since File available since Release 0.1.0
+ */
+
+/**
+ * Required PEAR classes
+ */
+require_once 'System.php';
+
+/**
+ * Interface to AT&T's GraphViz tools.
+ *
+ * The GraphViz class allows for the creation of and to work with directed
+ * and undirected graphs and their visualization with AT&T's GraphViz tools.
+ *
+ * <code>
+ * <?php
+ * require_once 'Image/GraphViz.php';
+ *
+ * $graph = new Image_GraphViz();
+ *
+ * $graph->addNode(
+ * 'Node1',
+ * array(
+ * 'URL' => 'http://link1',
+ * 'label' => 'This is a label',
+ * 'shape' => 'box'
+ * )
+ * );
+ *
+ * $graph->addNode(
+ * 'Node2',
+ * array(
+ * 'URL' => 'http://link2',
+ * 'fontsize' => '14'
+ * )
+ * );
+ *
+ * $graph->addNode(
+ * 'Node3',
+ * array(
+ * 'URL' => 'http://link3',
+ * 'fontsize' => '20'
+ * )
+ * );
+ *
+ * $graph->addEdge(
+ * array(
+ * 'Node1' => 'Node2'
+ * ),
+ * array(
+ * 'label' => 'Edge Label'
+ * )
+ * );
+ *
+ * $graph->addEdge(
+ * array(
+ * 'Node1' => 'Node2'
+ * ),
+ * array(
+ * 'color' => 'red'
+ * )
+ * );
+ *
+ * $graph->image();
+ * ?>
+ * </code>
+ *
+ * @category Image
+ * @package Image_GraphViz
+ * @author Sebastian Bergmann <sb@sebastian-bergmann.de>
+ * @author Dr. Volker Göbbels <vmg@arachnion.de>
+ * @author Karsten Dambekalns <k.dambekalns@fishfarm.de>
+ * @author Michael Lively Jr. <mlively@ft11.net>
+ * @author Philippe Jausions <Philippe.Jausions@11abacus.com>
+ * @copyright 2001-2007 Dr. Volker Göbbels <vmg@arachnion.de> and Sebastian Bergmann <sb@sebastian-bergmann.de>
+ * @license http://www.php.net/license/3_0.txt The PHP License, Version 3.0
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Image_GraphViz
+ * @link http://www.graphviz.org/
+ * @since Class available since Release 0.1
+ */
+class Image_GraphViz
+{
+ /**
+ * Base path to GraphViz commands
+ *
+ * @var string
+ */
+ var $binPath = '';
+
+ /**
+ * Path to GraphViz/dot command
+ *
+ * @var string
+ */
+ var $dotCommand = 'dot';
+
+ /**
+ * Path to GraphViz/neato command
+ *
+ * @var string
+ */
+ var $neatoCommand = 'neato';
+
+ /**
+ * Representation of the graph
+ *
+ * @var array
+ */
+ var $graph = array('edgesFrom' => array(),
+ 'nodes' => array(),
+ 'attributes' => array(),
+ 'directed' => true,
+ 'clusters' => array(),
+ 'subgraphs' => array(),
+ 'name' => 'G',
+ 'strict' => true,
+ );
+
+ /**
+ * Whether to return PEAR_Error instance on failures instead of FALSE
+ *
+ * @var boolean
+ */
+ protected $_returnFalseOnError = true;
+
+ /**
+ * Constructor.
+ *
+ * Setting the name of the Graph is useful for including multiple image
+ * maps on one page. If not set, the graph will be named 'G'.
+ *
+ * @param boolean $directed Directed (TRUE) or undirected (FALSE) graph.
+ * Note: You MUST pass a boolean, and not just
+ * an expression that evaluates to TRUE or
+ * FALSE (i.e. NULL, empty string, 0 will NOT
+ * work)
+ * @param array $attributes Attributes of the graph
+ * @param string $name Name of the Graph
+ * @param boolean $strict Whether to collapse multiple edges between
+ * same nodes
+ * @param boolean $returnError Set to TRUE to return PEAR_Error instances
+ * on failures instead of FALSE
+ */
+ public function Image_GraphViz($directed = true, $attributes = array(),
+ $name = 'G', $strict = true, $returnError = false)
+ {
+ $this->setDirected($directed);
+ $this->setAttributes($attributes);
+ $this->graph['name'] = $name;
+ $this->graph['strict'] = (boolean)$strict;
+
+ $this->_returnFalseOnError = !$returnError;
+ }
+
+ /**
+ * Outputs image of the graph in a given format
+ *
+ * This methods send HTTP headers
+ *
+ * @param string $format Format of the output image. This may be one
+ * of the formats supported by GraphViz.
+ * @param string $command "dot" or "neato"
+ *
+ * @return boolean TRUE on success, FALSE or PEAR_Error otherwise
+ */
+ public function image($format = 'svg', $command = null)
+ {
+ $file = $this->saveParsedGraph();
+ if (!$file || PEAR::isError($file)) {
+ return $file;
+ }
+
+ $outputfile = $file . '.' . $format;
+
+ $rendered = $this->renderDotFile($file, $outputfile, $format,
+ $command);
+ if ($rendered !== true) {
+ return $rendered;
+ }
+
+ $sendContentLengthHeader = true;
+
+ switch (strtolower($format)) {
+ case 'gif':
+ case 'png':
+ case 'bmp':
+ case 'jpeg':
+ case 'tiff':
+ header('Content-Type: image/' . $format);
+ break;
+
+ case 'tif':
+ header('Content-Type: image/tiff');
+ break;
+
+ case 'jpg':
+ header('Content-Type: image/jpeg');
+ break;
+
+ case 'ico':
+ header('Content-Type: image/x-icon');
+ break;
+
+ case 'wbmp':
+ header('Content-Type: image/vnd.wap.wbmp');
+ break;
+
+ case 'pdf':
+ header('Content-Type: application/pdf');
+ break;
+
+ case 'mif':
+ header('Content-Type: application/vnd.mif');
+ break;
+
+ case 'vrml':
+ header('Content-Type: application/x-vrml');
+ break;
+
+ case 'svg':
+ header('Content-Type: image/svg+xml');
+ break;
+
+ case 'plain':
+ case 'plain-ext':
+ header('Content-Type: text/plain');
+ break;
+
+ default:
+ header('Content-Type: application/octet-stream');
+ $sendContentLengthHeader = false;
+ }
+
+ if ($sendContentLengthHeader) {
+ header('Content-Length: ' . filesize($outputfile));
+ }
+
+ $return = true;
+ if (readfile($outputfile) === false) {
+ $return = false;
+ }
+ @unlink($outputfile);
+
+ return $return;
+ }
+
+ /**
+ * Returns image (data) of the graph in a given format.
+ *
+ * @param string $format Format of the output image. This may be one
+ * of the formats supported by GraphViz.
+ * @param string $command "dot" or "neato"
+ *
+ * @return string The image (data) created by GraphViz, FALSE or PEAR_Error
+ * on error
+ * @since Method available since Release 1.1.0
+ */
+ public function fetch($format = 'svg', $command = null)
+ {
+ $file = $this->saveParsedGraph();
+ if (!$file || PEAR::isError($file)) {
+ return $file;
+ }
+
+ $outputfile = $file . '.' . $format;
+
+ $rendered = $this->renderDotFile($file, $outputfile, $format,
+ $command);
+ if ($rendered !== true) {
+ return $rendered;
+ }
+
+ @unlink($file);
+
+ $fp = fopen($outputfile, 'rb');
+
+ if (!$fp) {
+ if ($this->_returnFalseOnError) {
+ return false;
+ }
+ $error = PEAR::raiseError('Could not read rendered file');
+ return $error;
+ }
+
+ $data = fread($fp, filesize($outputfile));
+ fclose($fp);
+ @unlink($outputfile);
+
+ return $data;
+ }
+
+ /**
+ * Renders a given dot file into a given format.
+ *
+ * @param string $dotfile The absolute path of the dot file to use.
+ * @param string $outputfile The absolute path of the file to save to.
+ * @param string $format Format of the output image. This may be one
+ * of the formats supported by GraphViz.
+ * @param string $command "dot" or "neato"
+ *
+ * @return boolean TRUE if the file was saved, FALSE or PEAR_Error
+ * otherwise.
+ */
+ public function renderDotFile($dotfile, $outputfile, $format = 'svg',
+ $command = null)
+ {
+ if (!file_exists($dotfile)) {
+ if ($this->_returnFalseOnError) {
+ return false;
+ }
+ $error = PEAR::raiseError('Could not find dot file');
+ return $error;
+ }
+
+ $oldmtime = file_exists($outputfile) ? filemtime($outputfile) : 0;
+
+ switch ($command) {
+ case 'dot':
+ case 'neato':
+ break;
+ default:
+ $command = $this->graph['directed'] ? 'dot' : 'neato';
+ }
+ $command_orig = $command;
+
+ $command = $this->binPath.(($command == 'dot') ? $this->dotCommand
+ : $this->neatoCommand);
+
+ $command .= ' -T'.escapeshellarg($format)
+ .' -o'.escapeshellarg($outputfile)
+ .' '.escapeshellarg($dotfile)
+ .' 2>&1';
+ exec($command, $msg, $return_val);
+
+ clearstatcache();
+ if (file_exists($outputfile) && filemtime($outputfile) > $oldmtime
+ && $return_val == 0) {
+ return true;
+ } elseif ($this->_returnFalseOnError) {
+ return false;
+ }
+ $error = PEAR::raiseError($command_orig.' command failed: '
+ .implode("\n", $msg));
+ return $error;
+ }
+
+ /**
+ * Adds a cluster to the graph.
+ *
+ * A cluster is a subgraph with a rectangle around it.
+ *
+ * @param string $id ID.
+ * @param array $title Title.
+ * @param array $attributes Attributes of the cluster.
+ * @param string $group ID of group to nest cluster into
+ *
+ * @return void
+ * @see addSubgraph()
+ */
+ public function addCluster($id, $title, $attributes = array(), $group = 'default')
+ {
+ $this->graph['clusters'][$id]['title'] = $title;
+ $this->graph['clusters'][$id]['attributes'] = $attributes;
+ $this->graph['clusters'][$id]['embedIn'] = $group;
+ }
+
+ /**
+ * Adds a subgraph to the graph.
+ *
+ * @param string $id ID.
+ * @param array $title Title.
+ * @param array $attributes Attributes of the cluster.
+ * @param string $group ID of group to nest subgraph into
+ *
+ * @return void
+ */
+ public function addSubgraph($id, $title, $attributes = array(), $group = 'default')
+ {
+ $this->graph['subgraphs'][$id]['title'] = $title;
+ $this->graph['subgraphs'][$id]['attributes'] = $attributes;
+ $this->graph['subgraphs'][$id]['embedIn'] = $group;
+ }
+
+ /**
+ * Adds a note to the graph.
+ *
+ * @param string $name Name of the node.
+ * @param array $attributes Attributes of the node.
+ * @param string $group Group of the node.
+ *
+ * @return void
+ */
+ public function addNode($name, $attributes = array(), $group = 'default')
+ {
+ $this->graph['nodes'][$group][$name] = $attributes;
+ }
+
+ /**
+ * Removes a node from the graph.
+ *
+ * This method doesn't remove edges associated with the node.
+ *
+ * @param string $name Name of the node to be removed.
+ * @param string $group Group of the node.
+ *
+ * @return void
+ */
+ public function removeNode($name, $group = 'default')
+ {
+ if (isset($this->graph['nodes'][$group][$name])) {
+ unset($this->graph['nodes'][$group][$name]);
+ }
+ }
+
+ /**
+ * Adds an edge to the graph.
+ *
+ * Examples:
+ * <code>
+ * $g->addEdge(array('node1' => 'node2'));
+ * $attr = array(
+ * 'label' => '+1',
+ * 'style' => 'dashed',
+ * );
+ * $g->addEdge(array('node3' => 'node4'), $attr);
+ *
+ * // With port specification
+ * $g->addEdge(array('node5' => 'node6'), $attr, array('node6' => 'portA'));
+ * $g->addEdge(array('node7' => 'node8'), null, array('node7' => 'portC',
+ * 'node8' => 'portD'));
+ * </code>
+ *
+ * @param array $edge Start => End node of the edge.
+ * @param array $attributes Attributes of the edge.
+ * @param array $ports Start node => port, End node => port
+ *
+ * @return integer an edge ID that can be used with {@link removeEdge()}
+ */
+ public function addEdge($edge, $attributes = array(), $ports = array())
+ {
+ if (!is_array($edge)) {
+ return;
+ }
+
+ $from = key($edge);
+ $to = $edge[$from];
+ $info = array();
+
+ if (is_array($ports)) {
+ if (array_key_exists($from, $ports)) {
+ $info['portFrom'] = $ports[$from];
+ }
+
+ if (array_key_exists($to, $ports)) {
+ $info['portTo'] = $ports[$to];
+ }
+ }
+
+ if (is_array($attributes)) {
+ $info['attributes'] = $attributes;
+ }
+
+ if (!empty($this->graph['strict'])) {
+ if (!isset($this->graph['edgesFrom'][$from][$to][0])) {
+ $this->graph['edgesFrom'][$from][$to][0] = $info;
+ } else {
+ $this->graph['edgesFrom'][$from][$to][0] = array_merge($this->graph['edgesFrom'][$from][$to][0], $info);
+ }
+ } else {
+ $this->graph['edgesFrom'][$from][$to][] = $info;
+ }
+
+ return count($this->graph['edgesFrom'][$from][$to]) - 1;
+ }
+
+ /**
+ * Removes an edge from the graph.
+ *
+ * @param array $edge Start and End node of the edge to be removed.
+ * @param integer $id specific edge ID (only usefull when multiple edges
+ * exist between the same 2 nodes)
+ *
+ * @return void
+ */
+ public function removeEdge($edge, $id = null)
+ {
+ if (!is_array($edge)) {
+ return;
+ }
+
+ $from = key($edge);
+ $to = $edge[$from];
+
+ if (!is_null($id)) {
+ if (isset($this->graph['edgesFrom'][$from][$to][$id])) {
+ unset($this->graph['edgesFrom'][$from][$to][$id]);
+
+ if (count($this->graph['edgesFrom'][$from][$to]) == 0) {
+ unset($this->graph['edgesFrom'][$from][$to]);
+ }
+ }
+ } elseif (isset($this->graph['edgesFrom'][$from][$to])) {
+ unset($this->graph['edgesFrom'][$from][$to]);
+ }
+ }
+
+ /**
+ * Adds attributes to the graph.
+ *
+ * @param array $attributes Attributes to be added to the graph.
+ *
+ * @return void
+ */
+ public function addAttributes($attributes)
+ {
+ if (is_array($attributes)) {
+ $this->graph['attributes'] = array_merge($this->graph['attributes'], $attributes);
+ }
+ }
+
+ /**
+ * Sets attributes of the graph.
+ *
+ * @param array $attributes Attributes to be set for the graph.
+ *
+ * @return void
+ */
+ public function setAttributes($attributes)
+ {
+ if (is_array($attributes)) {
+ $this->graph['attributes'] = $attributes;
+ }
+ }
+
+ /**
+ * Escapes an (attribute) array
+ *
+ * Detects if an attribute is <html>, contains double-quotes, etc...
+ *
+ * @param array $input input to escape
+ *
+ * @return array input escaped
+ */
+ protected function _escapeArray($input)
+ {
+ $output = array();
+
+ foreach ((array)$input as $k => $v) {
+ switch ($k) {
+ case 'label':
+ case 'headlabel':
+ case 'taillabel':
+ $v = $this->_escape($v, true);
+ break;
+ default:
+ $v = $this->_escape($v);
+ $k = $this->_escape($k);
+ }
+
+ $output[$k] = $v;
+ }
+
+ return $output;
+ }
+
+ /**
+ * Returns a safe "ID" in DOT syntax
+ *
+ * @param string $input string to use as "ID"
+ * @param boolean $html whether to attempt detecting HTML-like content
+ *
+ * @return string
+ */
+ protected function _escape($input, $html = false)
+ {
+ switch (strtolower($input)) {
+ case 'node':
+ case 'edge':
+ case 'graph':
+ case 'digraph':
+ case 'subgraph':
+ case 'strict':
+ return '"'.$input.'"';
+ }
+
+ if (is_bool($input)) {
+ return ($input) ? 'true' : 'false';
+ }
+
+ if ($html && (strpos($input, '</') !== false
+ || strpos($input, '/>') !== false)) {
+ return '<'.$input.'>';
+ }
+
+ if (preg_match('/^([a-z_][a-z_0-9]*|-?(\.[0-9]+|[0-9]+(\.[0-9]*)?))$/i',
+ $input)) {
+ return $input;
+ }
+
+ return '"'.str_replace(array("\r\n", "\n", "\r", '"'),
+ array('\n', '\n', '\n', '\"'), $input).'"';
+ }
+
+ /**
+ * Sets directed/undirected flag for the graph.
+ *
+ * Note: You MUST pass a boolean, and not just an expression that evaluates
+ * to TRUE or FALSE (i.e. NULL, empty string, 0 will not work)
+ *
+ * @param boolean $directed Directed (TRUE) or undirected (FALSE) graph.
+ *
+ * @return void
+ */
+ public function setDirected($directed)
+ {
+ if (is_bool($directed)) {
+ $this->graph['directed'] = $directed;
+ }
+ }
+
+ /**
+ * Loads a graph from a file in Image_GraphViz format
+ *
+ * @param string $file File to load graph from.
+ *
+ * @return void
+ */
+ public function load($file)
+ {
+ if ($serializedGraph = implode('', @file($file))) {
+ $g = unserialize($serializedGraph);
+
+ if (!is_array($g)) {
+ return;
+ }
+
+ // Convert old storage format to new one
+ $defaults = array('edgesFrom' => array(),
+ 'nodes' => array(),
+ 'attributes' => array(),
+ 'directed' => true,
+ 'clusters' => array(),
+ 'subgraphs' => array(),
+ 'name' => 'G',
+ 'strict' => true,
+ );
+
+ $this->graph = array_merge($defaults, $g);
+
+ if (isset($this->graph['edges'])) {
+ foreach ($this->graph['edges'] as $id => $nodes) {
+ $attr = (isset($this->graph['edgeAttributes'][$id]))
+ ? $this->graph['edgeAttributes'][$id]
+ : array();
+
+ $this->addEdge($nodes, $attr);
+ }
+
+ unset($this->graph['edges']);
+ unset($this->graph['edgeAttributes']);
+ }
+ }
+ }
+
+ /**
+ * Save graph to file in Image_GraphViz format
+ *
+ * This saves the serialized version of the instance, not the
+ * rendered graph.
+ *
+ * @param string $file File to save the graph to.
+ *
+ * @return string File the graph was saved to, FALSE or PEAR_Error on
+ * failure.
+ */
+ public function save($file = '')
+ {
+ $serializedGraph = serialize($this->graph);
+
+ if (empty($file)) {
+ $file = System::mktemp('graph_');
+ }
+
+ if ($fp = @fopen($file, 'wb')) {
+ @fputs($fp, $serializedGraph);
+ @fclose($fp);
+
+ return $file;
+ }
+
+ if ($this->_returnFalseOnError) {
+ return false;
+ }
+ $error = PEAR::raiseError('Could not save serialized graph instance');
+ return $error;
+ }
+
+ /**
+ * Returns a list of sub-groups for a given parent group
+ *
+ * @param string $parent Group ID
+ *
+ * @return array list of group IDs
+ */
+ protected function _getSubgraphs($parent)
+ {
+ $subgraphs = array();
+ foreach ($this->graph['clusters'] as $id => $info) {
+ if ($info['embedIn'] === $parent) {
+ $subgraphs[] = $id;
+ }
+ }
+ foreach ($this->graph['subgraphs'] as $id => $info) {
+ if ($info['embedIn'] === $parent) {
+ $subgraphs[] = $id;
+ }
+ }
+ return $subgraphs;
+ }
+
+ /**
+ * Returns a list of cluster/subgraph IDs
+ *
+ * @return array
+ */
+ protected function _getGroups()
+ {
+ $groups = array_merge(array_keys($this->graph['clusters']),
+ array_keys($this->graph['subgraphs']));
+ return array_unique($groups);
+ }
+
+ /**
+ * Returns a list of top groups
+ *
+ * @return array
+ */
+ protected function _getTopGraphs()
+ {
+ $top = array();
+ $groups = $this->_getGroups();
+
+ foreach ($groups as $id) {
+ $isTop = ($id === 'default');
+ if (isset($this->graph['clusters'][$id])
+ && $this->graph['clusters'][$id]['embedIn'] === 'default') {
+ $isTop = true;
+ }
+ if (isset($this->graph['subgraphs'][$id])
+ && $this->graph['subgraphs'][$id]['embedIn'] === 'default') {
+ $isTop = true;
+ }
+ if ($isTop) {
+ $top[] = $id;
+ }
+ }
+
+ return array_unique($top);
+ }
+
+ /**
+ * Parses the graph into GraphViz markup.
+ *
+ * @return string GraphViz markup
+ */
+ public function parse()
+ {
+ $parsedGraph = (empty($this->graph['strict'])) ? '' : 'strict ';
+ $parsedGraph .= (empty($this->graph['directed'])) ? 'graph ' : 'digraph ';
+ $parsedGraph .= $this->_escape($this->graph['name'])." {\n";
+
+ $indent = ' ';
+
+ $attr = $this->_escapeArray($this->graph['attributes']);
+
+ foreach ($attr as $key => $value) {
+ $parsedGraph .= $indent.$key.'='.$value.";\n";
+ }
+
+ $groups = $this->_getGroups();
+ foreach ($this->graph['nodes'] as $group => $nodes) {
+ if (!in_array($group, $groups)) {
+ $parsedGraph .= $this->_nodes($nodes, $indent);
+ }
+ }
+ $tops = $this->_getTopGraphs();
+ foreach ($tops as $group) {
+ $parsedGraph .= $this->_subgraph($group, $indent);
+ }
+
+ if (!empty($this->graph['directed'])) {
+ $separator = ' -> ';
+ } else {
+ $separator = ' -- ';
+ }
+
+ foreach ($this->graph['edgesFrom'] as $from => $toNodes) {
+ $from = $this->_escape($from);
+
+ foreach ($toNodes as $to => $edges) {
+ $to = $this->_escape($to);
+
+ foreach ($edges as $info) {
+ $f = $from;
+ $t = $to;
+
+ if (array_key_exists('portFrom', $info)) {
+ $f .= ':'.$this->_escape($info['portFrom']);
+ }
+
+ if (array_key_exists('portTo', $info)) {
+ $t .= ':'.$this->_escape($info['portTo']);
+ }
+
+ $parsedGraph .= $indent.$f.$separator.$t;
+
+ if (!empty($info['attributes'])) {
+ $attributeList = array();
+
+ foreach ($this->_escapeArray($info['attributes']) as $key => $value) {
+ switch ($key) {
+ case 'lhead':
+ case 'ltail':
+ if (strncasecmp($value, 'cluster', 7)) {
+ $value = 'cluster_'.$value;
+ }
+ break;
+ }
+ $attributeList[] = $key.'='.$value;
+ }
+
+ $parsedGraph .= ' [ '.implode(',', $attributeList).' ]';
+ }
+
+ $parsedGraph .= ";\n";
+ }
+ }
+ }
+
+ return $parsedGraph . "}\n";
+ }
+
+ /**
+ * Output nodes
+ *
+ * @param array $nodes nodes list
+ * @param string $indent space indentation
+ *
+ * @return string output
+ */
+ protected function _nodes($nodes, $indent)
+ {
+ $parsedGraph = '';
+ foreach ($nodes as $node => $attributes) {
+ $parsedGraph .= $indent.$this->_escape($node);
+
+ $attributeList = array();
+
+ foreach ($this->_escapeArray($attributes) as $key => $value) {
+ $attributeList[] = $key.'='.$value;
+ }
+
+ if (!empty($attributeList)) {
+ $parsedGraph .= ' [ '.implode(',', $attributeList).' ]';
+ }
+
+ $parsedGraph .= ";\n";
+ }
+ return $parsedGraph;
+ }
+
+ /**
+ * Generates output for a group
+ *
+ * @return string output
+ */
+ protected function _subgraph($group, &$indent)
+ {
+ $parsedGraph = '';
+ $nodes = $this->graph['nodes'][$group];
+
+ if ($group !== 'default') {
+ $type = null;
+ $_group = $this->_escape($group);
+
+ if (isset($this->graph['clusters'][$group])) {
+ $type = 'clusters';
+ if (strncasecmp($group, 'cluster', 7)) {
+ $_group = $this->_escape('cluster_'.$group);
+ }
+ } elseif (isset($this->graph['subgraphs'][$group])) {
+ $type = 'subgraphs';
+ }
+ $parsedGraph .= $indent.'subgraph '.$_group." {\n";
+
+ $indent .= ' ';
+
+ if ($type !== null && isset($this->graph[$type][$group])) {
+ $cluster = $this->graph[$type][$group];
+ $_attr = $this->_escapeArray($cluster['attributes']);
+
+ $attr = array();
+ foreach ($_attr as $key => $value) {
+ $attr[] = $key.'='.$value;
+ }
+
+ if (strlen($cluster['title'])) {
+ $attr[] = 'label='
+ .$this->_escape($cluster['title'], true);
+ }
+
+ if ($attr) {
+ $parsedGraph .= $indent.'graph [ '.implode(',', $attr)
+ ." ];\n";
+ }
+ }
+ }
+
+ $parsedGraph .= $this->_nodes($nodes, $indent);
+
+ foreach ($this->_getSubgraphs($group) as $_group) {
+ $parsedGraph .= $this->_subgraph($_group, $indent);
+ }
+
+ if ($group !== 'default') {
+ $indent = substr($indent, 0, -4);
+
+ $parsedGraph .= $indent."}\n";
+ }
+
+ return $parsedGraph;
+ }
+
+ /**
+ * Saves GraphViz markup to file (in DOT language)
+ *
+ * @param string $file File to write the GraphViz markup to.
+ *
+ * @return string File to which the GraphViz markup was written, FALSE or
+ * or PEAR_Error on failure.
+ */
+ public function saveParsedGraph($file = '')
+ {
+ $parsedGraph = $this->parse();
+
+ if (!empty($parsedGraph)) {
+ if (empty($file)) {
+ $file = System::mktemp('graph_');
+ }
+
+ if ($fp = @fopen($file, 'wb')) {
+ @fputs($fp, $parsedGraph);
+ @fclose($fp);
+
+ return $file;
+ }
+ }
+
+ if ($this->_returnFalseOnError) {
+ return false;
+ }
+ $error = PEAR::raiseError('Could not save graph');
+ return $error;
+ }
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * c-hanging-comment-ender-p: nil
+ * End:
+ */
+?>