diff --git a/broken.svg b/broken.svg
new file mode 100644
index 0000000..e3b6d97
--- /dev/null
+++ b/broken.svg
@@ -0,0 +1,23 @@
+
+
+
+
+
diff --git a/conf/default.php b/conf/default.php
index 76c8989..114a181 100644
--- a/conf/default.php
+++ b/conf/default.php
@@ -1,3 +1,5 @@
_imgfile($data);
-if(!$cache) _fail();
+// Update: show svg or png depending on format.
+if(!$cache) _fail($data['format']);
+// Update: support both png and svg
+$img_format = ($data['format'] == 'png' ? 'png' : 'svg');
+if ($img_format == 'svg'){
+ header('Content-Type: image/svg+xml');
+}
+else
+{
+ header('Content-Type: image/png');
+}
-header('Content-Type: image/png;');
header('Expires: '.gmdate("D, d M Y H:i:s", time()+max($conf['cachetime'], 3600)).' GMT');
header('Cache-Control: public, proxy-revalidate, no-transform, max-age='.max($conf['cachetime'], 3600));
header('Pragma: public');
@@ -22,10 +31,16 @@
echo io_readFile($cache,false);
-function _fail(){
- header("HTTP/1.0 404 Not Found");
- header('Content-Type: image/png');
- echo io_readFile('broken.png',false);
+function _fail($format){
+ if ($format == 'svg'){
+ header("HTTP/1.0 404 Not Found");
+ header('Content-Type: image/svg+xml');
+ echo io_readFile('broken.svg',false);
+ }else{
+ header("HTTP/1.0 404 Not Found");
+ header('Content-Type: image/png');
+ echo io_readFile('broken.png',false);
+ }
exit;
}
diff --git a/lang/en/settings.php b/lang/en/settings.php
index 69bf6b0..15c515a 100644
--- a/lang/en/settings.php
+++ b/lang/en/settings.php
@@ -1,3 +1,6 @@
/usr/bin/dot). Leave empty to use remote rendering at google.com.';
+$lang['use_svg'] = 'Use embeded svg objects. Svg objects support clickable dokuwiki links in the graphs. This is automatically disabled if "path" is left empty.';
+$lang['styles'] = 'A block of DOT codes, that can be included to the graphs. Meant to include styling. To set a style put it inside <style name="stylename"> style code here lgt;/style>. The stylename can only contain alphanumeric and underscore characters.'.
+ 'E.g. '.htmlspecialchars('').'';
diff --git a/syntax.php b/syntax.php
index 518d027..1530f57 100644
--- a/syntax.php
+++ b/syntax.php
@@ -8,12 +8,26 @@
*/
-if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
-if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
+if (!defined('DOKU_INC')) define('DOKU_INC',realpath(DOKU_PLUGIN.'/../../').'/');
require_once(DOKU_PLUGIN.'syntax.php');
class syntax_plugin_graphviz extends DokuWiki_Syntax_Plugin {
+ /**
+ * array holding defined styles.
+ */
+ protected $_styles = array();
+
+
+ /**
+ * Constructor to check config
+ */
+ function __construct(){
+ // disable use_svg if 'path' is not set.
+ if(!$this->getConf('path')) $this->conf['use_svg'] = 0;
+ $this->_parseStyles($s);
+ }
+
/**
* What about paragraphs?
*/
@@ -39,7 +53,8 @@ function getSort(){
* Connect pattern to lexer
*/
function connectTo($mode) {
- $this->Lexer->addSpecialPattern('\n.*?\n',$mode,'plugin_graphviz');
+ //Fix: removed newline requirements, as a accidental space before newline can break the pattern
+ $this->Lexer->addSpecialPattern('.*?',$mode,'plugin_graphviz');
}
/**
@@ -47,7 +62,6 @@ function connectTo($mode) {
*/
function handle($match, $state, $pos, &$handler) {
$info = $this->getInfo();
-
// prepare default data
$return = array(
'width' => 0,
@@ -57,10 +71,17 @@ function handle($match, $state, $pos, &$handler) {
'version' => $info['date'], //force rebuild of images on update
);
- // prepare input
- $lines = explode("\n",$match);
- $conf = array_shift($lines);
- array_pop($lines);
+ // prepare input, UPDATE: separate tags from data by preg instead of first/last line, but leave the old behavior as fallback
+ if (preg_match('~]*)>((?:[\n]|.)*)~Ui',$match,$parts)){
+ $input = $parts[2];
+ $conf = $parts[1];
+ }
+ else{
+ $lines = explode("\n",$match);
+ $conf = array_shift($lines);
+ array_pop($lines);
+ $input = join("\n",$lines);
+ }
// match config options
if(preg_match('/\b(left|center|right)\b/i',$conf,$match)) $return['align'] = $match[1];
@@ -73,17 +94,69 @@ function handle($match, $state, $pos, &$handler) {
}
if(preg_match('/\bwidth=([0-9]+)\b/i', $conf,$match)) $return['width'] = $match[1];
if(preg_match('/\bheight=([0-9]+)\b/i', $conf,$match)) $return['height'] = $match[1];
-
-
- $input = join("\n",$lines);
+
+ // Added code for styles.
+ $styles = array();
+ // match stylenames that are written into the graphviz tag
+ if(preg_match_all('/\b(\w+)\b/i',$conf,$match)){
+ foreach ($match[1] as $style){
+ if (array_key_exists($tmp = strtolower($style),$this->_styles)){
+ $styles[] = $tmp;
+ }
+ }
+ }
+ $styles = array_unique($styles); // remove duplicated styles
+
+ $this->_injectStyles($input,$styles);
+
+ //Update: add dokulink support to URL tags (they can contain [[dokulink]] urls.
+ if ($this->getConf('use_svg')){
+ global $ID;
+ $return['url'] = md5(wl($ID,true)); // add current page's url to 'data' to ensure exported links work on page
+ $input = preg_replace_callback(
+ '~\b(|head|label|tail|edge)'. // match 0 -> prefix for URL
+ 'URL="\[\[(?:'. // alternative urls to catch
+ '(\w+\://[^\]\|]*)'. // simple url with any shema
+ '|(?:(\w+)>([^\]\|]*))'. // interwiki link
+ '|([^\]\|#]+)?(#[^\]\|]*)?)'. // dokuwiki link and/or fragment
+ '(?:\|([^\]]*))?'. // text
+ '\]\]"~U',
+ array(__CLASS__,'_parse_links'),
+ $input);
+ }
$return['md5'] = md5($input); // we only pass a hash around
-
// store input for later use
io_saveFile($this->_cachename($return,'txt'),$input);
-
return $return;
}
+ /**
+ * callback function to preg_replace_callback, that fixes urls.
+ */
+ public static function _parse_links($matches){
+ global $ID;
+ $pref = @$matches[1]; // the prefix of the URL (i.e. "head" from "headURL")
+ $url = @$matches[2]; // external link (it's already url)
+ $int_t = @$matches[3]; // interwiki prefix (i.e. "wp" from "[[wp>blabla]]")
+ $int_u = @$matches[4]; // interwiki url (i.e. "blabla" from "[[wp>blabla]]")
+ $page = @$matches[5]; // dokuwiki internal page link
+ $frag = @$matches[6] ? '#'.preg_replace('~\W+~','-',strtolower(ltrim($matches[6],"#"))) : ""; // fragment for dokuwiki internal link, simple cleaning
+ $text = @$matches[7]; // text of link (i.e. "blabla" from "[[.:mylink:|blabla]]"
+ if ($page || $frag){
+ resolve_pageid(getNS($ID),$page,$exists);
+ $url = wl($page).$frag;
+ }
+ elseif($int_t){
+ static $xhtml_renderer = null;
+ if(is_null($xhtml_renderer)){
+ $xhtml_renderer = p_get_renderer('xhtml');
+ $xhtml_renderer->interwiki = getInterwiki();
+ }
+ $url = $xhtml_renderer->_resolveInterWiki($int_t,$int_u,$exists);
+ }
+ return "{$pref}URL=\"{$url}\";{$pref}target=_top".($text ? ";{$pref}tooltip=\"{$text}\"" : "");
+ }
+
/**
* Cache file is based on parameters that influence the result image
*/
@@ -91,6 +164,7 @@ function _cachename($data,$ext){
unset($data['width']);
unset($data['height']);
unset($data['align']);
+ unset($data['format']);
return getcachename(join('x',array_values($data)),'.graphviz.'.$ext);
}
@@ -98,14 +172,35 @@ function _cachename($data,$ext){
* Create output
*/
function render($format, &$R, $data) {
- if($format == 'xhtml'){
- $img = DOKU_BASE.'lib/plugins/graphviz/img.php?'.buildURLparams($data);
- $R->doc .= '
getConf('use_svg') && get_class($R) != 'renderer_plugin_dw2pdf'){
+ //Update: generate both png and svg, embed svg but with fallback to png.
+ $img_svg = DOKU_BASE.'lib/plugins/graphviz/img.php?'.buildURLparams(array_merge($data,array('format'=>'svg')));
+ // embed svg as object: the links in svg can be clicked (and also animations are supported)
+ $R->doc .= '';
+ }
return true;
}elseif($format == 'odt'){
$src = $this->_imgfile($data);
@@ -119,7 +214,9 @@ function render($format, &$R, $data) {
* Return path to the rendered image on our local system
*/
function _imgfile($data){
- $cache = $this->_cachename($data,'png');
+ // Update: support pnd and svg format as well.
+ $format = ($data['format'] == 'png' ? 'png' : 'svg');
+ $cache = $this->_cachename($data,$format);
// create the file if needed
if(!file_exists($cache)){
@@ -182,11 +279,11 @@ function _run($data,$in,$out) {
}
$cmd = $this->getConf('path');
- $cmd .= ' -Tpng';
+ // Update: support both svg and png
+ $cmd .= ' -T'.($data['format'] == 'png' ? 'png' : 'svg');
$cmd .= ' -K'.$data['layout'];
$cmd .= ' -o'.escapeshellarg($out); //output
$cmd .= ' '.escapeshellarg($in); //input
-
exec($cmd, $output, $error);
if ($error != 0){
@@ -198,6 +295,33 @@ function _run($data,$in,$out) {
return true;
}
+ /**
+ * parse styles form configuration
+ */
+ function _parseStyles(){
+ if (preg_match_all('~~U',$this->getConf('styles'),$matches,PREG_SET_ORDER)){
+ foreach ($matches as $match){
+ $this->_styles[strtolower($match[2])] = $match[3];
+ }
+ }
+ }
+
+ /**
+ * parse styles form configuration
+ */
+ function _injectStyles(&$input,$styles){
+ // if there are no styles or the beginning of the graph is not found, leave it as is
+ if (!empty($styles) && (($p = strpos($input,"{")) !== false)){
+ $beg = substr($input,0,$p+1); // beginning, i.e. "digraph name{"
+ $rest = substr($input,$p+1); // the rest of the string
+ $input = $beg;
+ foreach ($styles as $style){ // add each matched style right after the beginning.
+ $input .= "\n".$this->_styles[$style]."\n";
+ }
+ $input .= $rest; // the graph code itself.
+ }
+ }
+
}