| 1 |
<?php
|
| 2 |
// $Id$
|
| 3 |
/**
|
| 4 |
* @file Provides new "Graphviz" filter that interprets text as DOT syntax
|
| 5 |
* and outputs corresponding PNG image of the graph.
|
| 6 |
* Uses PEAR::Image_GraphViz (http://pear.php.net/package/Image_GraphViz)
|
| 7 |
*/
|
| 8 |
|
| 9 |
require_once('Image/GraphViz.php');
|
| 10 |
|
| 11 |
function graphviz_filter_filter($op, $delta = 0, $format = -1, $text = '') {
|
| 12 |
if ($op == 'list') {
|
| 13 |
return array(
|
| 14 |
0 => t('Graphviz (DOT) syntax'),
|
| 15 |
);
|
| 16 |
}
|
| 17 |
|
| 18 |
if ($op == 'description') {
|
| 19 |
return t('Interprets text as DOT syntax and returns a PNG image of the generated graph.');
|
| 20 |
}
|
| 21 |
|
| 22 |
if ($op == 'process') {
|
| 23 |
return _graphviz_filter_process($text);
|
| 24 |
}
|
| 25 |
|
| 26 |
return $text;
|
| 27 |
}
|
| 28 |
|
| 29 |
function graphviz_filter_filter_tips($delta, $format, $long = false) {
|
| 30 |
$output = t('Use <a href="@dot">DOT syntax</a> with some additional commands embedded in the comments.', array('@dot' => url('http://www.graphviz.org/Documentation.php')));
|
| 31 |
if ($long) {
|
| 32 |
$output .= t('<p />In the comments, you can specify additional arguments such as:<br />
|
| 33 |
<ul>
|
| 34 |
<li>@directed = is it a directed graph? [YES (default)|NO|TRUE|FALSE|1|0]</li>
|
| 35 |
<li>@command = any graphviz command such as dot, neato, twopi, etc.</li>
|
| 36 |
<li>@format = any graphviz output format such as png (default), gif, jpg, etc.</li>
|
| 37 |
<li>@title = the "title" attribute of the HTML element, i.e., the tooltip</li>
|
| 38 |
<li>@map = does it contain URLs? [YES|NO (default)|TRUE|FALSE|1|0]</li>
|
| 39 |
</ul>');
|
| 40 |
}
|
| 41 |
return $output;
|
| 42 |
}
|
| 43 |
|
| 44 |
function _graphviz_filter_process($text) {
|
| 45 |
// Create a temporary file with the DOT script.
|
| 46 |
$outdir = file_directory_path().'/graphviz';
|
| 47 |
file_check_directory($outdir, FILE_CREATE_DIRECTORY);
|
| 48 |
$inpath = System::mktemp();
|
| 49 |
file_put_contents($inpath, $text);
|
| 50 |
|
| 51 |
// Parse arguments and make sure required format is supported.
|
| 52 |
$args = array(
|
| 53 |
'directed' => TRUE,
|
| 54 |
'command' => '',
|
| 55 |
'formats' => 'png',
|
| 56 |
'title' => '',
|
| 57 |
'map' => FALSE,
|
| 58 |
);
|
| 59 |
_graphviz_parse_args($text, $args);
|
| 60 |
|
| 61 |
// Create and render the graph.
|
| 62 |
$G = new Image_GraphViz($args['directed']);
|
| 63 |
if ($args['command']) {
|
| 64 |
if ($args['directed']) {
|
| 65 |
$G->dotCommand = $args['command'];
|
| 66 |
}
|
| 67 |
else {
|
| 68 |
$G->neatoCommand = $args['command'];
|
| 69 |
}
|
| 70 |
}
|
| 71 |
if ($args['map']) { // FIXME We handle only client-side maps for now.
|
| 72 |
$mappath = file_create_filename(md5($text).'.map', $outdir);
|
| 73 |
if ($G->renderDotFile($inpath, $mappath, 'cmapx')) {
|
| 74 |
$args['mapfile'] = $mappath;
|
| 75 |
}
|
| 76 |
}
|
| 77 |
|
| 78 |
// Render desired formats.
|
| 79 |
$formats = array();
|
| 80 |
foreach (module_implements('graphviz_formats') as $module) {
|
| 81 |
$f = module_invoke($module, 'graphviz_formats');
|
| 82 |
$formats = array_merge($formats, array_fill_keys($f, $module));
|
| 83 |
}
|
| 84 |
$output = '';
|
| 85 |
foreach (explode(',', $args['formats']) as $format) {
|
| 86 |
$format = trim($format);
|
| 87 |
if (!array_key_exists($format, $formats)) {
|
| 88 |
$msg = t('Graphviz format %format is not supported. Please choose one of (%supported).',
|
| 89 |
array('%format' => $format, '%supported' => implode(', ', array_keys($formats)),
|
| 90 |
));
|
| 91 |
drupal_set_message($msg, 'error');
|
| 92 |
watchdog('graphviz_filter', $msg, WATCHDOG_ERROR);
|
| 93 |
}
|
| 94 |
$module = $formats[$format];
|
| 95 |
$outpath = file_create_filename(md5($text).'.'.$format, $outdir);
|
| 96 |
if ($G->renderDotFile($inpath, $outpath, $format)) {
|
| 97 |
$output .= module_invoke($module, 'graphviz_render', $inpath, $outpath, $format, $args);
|
| 98 |
$output .= '<p />';
|
| 99 |
}
|
| 100 |
else {
|
| 101 |
$msg = t('There was an error rendering the Graphviz file using format %format.', array('%format' => $format));
|
| 102 |
drupal_set_message($msg, 'error');
|
| 103 |
watchdog('graphviz_filter', $msg, WATCHDOG_ERROR);
|
| 104 |
}
|
| 105 |
}
|
| 106 |
return $output;
|
| 107 |
}
|
| 108 |
|
| 109 |
function _graphviz_parse_args($text, &$args) {
|
| 110 |
// Parse comments in the DOT syntax to find our arguments.
|
| 111 |
// Taken from a discussion in http://ostermiller.org/findcomment.html
|
| 112 |
if (!preg_match_all("/(?:\/\*(?:[^*]|(?:\*+[^*\/]))*\*+\/)/", $text, $comments)) return;
|
| 113 |
foreach ($comments[0] as $comment) {
|
| 114 |
foreach ($args as $key => $value) {
|
| 115 |
if (($m = _graphviz_parse_arg($comment, $key)) !== FALSE) {
|
| 116 |
if (is_bool($value)) {
|
| 117 |
$args[$key] = preg_match("/TRUE|YES|1/i", $m) == 1;
|
| 118 |
}
|
| 119 |
else {
|
| 120 |
$args[$key] = trim($m);
|
| 121 |
}
|
| 122 |
}
|
| 123 |
}
|
| 124 |
}
|
| 125 |
}
|
| 126 |
|
| 127 |
function _graphviz_parse_arg($text, $arg) {
|
| 128 |
// FIXME This doesn't handle case /* @arg=value */ i.e. start and close on same line.
|
| 129 |
if (preg_match("/@$arg\s*?=\s*?(\w[\w\W]*?)[\r\n]/i", $text, $match)) {
|
| 130 |
return $match[1];
|
| 131 |
}
|
| 132 |
return FALSE;
|
| 133 |
}
|
| 134 |
|
| 135 |
function graphviz_filter_graphviz_formats() {
|
| 136 |
return array(
|
| 137 |
'png',
|
| 138 |
'jpg',
|
| 139 |
'gif',
|
| 140 |
'jpeg',
|
| 141 |
'jpe',
|
| 142 |
);
|
| 143 |
}
|
| 144 |
|
| 145 |
function graphviz_filter_graphviz_render($inpath, $outpath, $format, $args) {
|
| 146 |
$output = '<div class="graphviz-image"><img src="'.file_create_url($outpath).'" title="'.check_plain($args['title']).'" alt="'.t('graphviz_filter rendering').'" ';
|
| 147 |
if ($args['map'] && $args['mapfile']) {
|
| 148 |
$output .= 'usemap="#G" />';
|
| 149 |
$output .= file_get_contents($args['mapfile']);
|
| 150 |
$output .= "\n";
|
| 151 |
}
|
| 152 |
else {
|
| 153 |
$output .= ' />';
|
| 154 |
}
|
| 155 |
$output .= "</div>\n";
|
| 156 |
return $output;
|
| 157 |
}
|
| 158 |
|