Current File : //opt/RZphp72/includes/Image/GraphViz.php |
<?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
* @access protected
*/
var $_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
*
* @access 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
* @access 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
* @access public
* @since Method available since Release 1.1.0
*/
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.
* @access 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
* @access public
* @see addSubgraph()
*/
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
* @access 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
* @access 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
* @access 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()}
* @access 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
* @access 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
* @access 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
* @access 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
* @access 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
* @access 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
* @access 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
* @access 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.
* @access 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
* @access 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
* @access 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
* @access 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
* @access 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
* @access 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
* @access 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.
* @access 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:
*/
?>