1<?php
2/**
3 * RestructuredText plugin for DokuWiki.
4 *
5 * @license GPL 3 (http://www.gnu.org/licenses/gpl.html)
6 * @version 1.0 - 2015-10-07
7 * @author Chris Green <chris@isbd.co.uk>
8 */
9
10if (!defined('DOKU_INC')) die();
11if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/');
12require_once (DOKU_PLUGIN . 'syntax.php');
13
14class syntax_plugin_rst extends DokuWiki_Syntax_Plugin {
15
16    function getType() {
17        return 'protected';
18    }
19
20    function getPType() {
21        return 'block';
22    }
23
24    function getSort() {
25        return 69;
26    }
27
28    function connectTo($mode) {
29        $this->Lexer->addEntryPattern('<rst>(?=.*</rst>)', $mode, 'plugin_rst');
30    }
31
32    function postConnect() {
33        $this->Lexer->addExitPattern('</rst>', 'plugin_rst');
34    }
35
36    function handle($match, $state, $pos, &$handler) {
37        switch ($state) {
38            case DOKU_LEXER_ENTER :      return array($state, '');
39            case DOKU_LEXER_UNMATCHED :  return array($match);
40            case DOKU_LEXER_EXIT :       return array($state, '');
41        }
42        return array($state,'');
43    }
44
45    function render($mode, &$renderer, $data) {
46        dbglog("mode is $mode, data[0] is $data[0]");
47        if ($mode == 'xhtml' && $data[0] != DOKU_LEXER_ENTER && $data[0] != DOKU_LEXER_EXIT)
48        {
49            $descriptorspec = array(
50               0 => array("pipe", "r"),  // stdin is a pipe that the child will read from
51               1 => array("pipe", "w"),  // stdout is a pipe that the child will write to
52               2 => array("file", "/tmp/error-output.txt", "a") // stderr is a file to write to
53            );
54
55            $cwd = '/tmp';
56            $env = NULL;
57
58            $process = proc_open('rst2html --no-doc-title', $descriptorspec, $pipes, $cwd, $env);
59
60            if (is_resource($process)) {
61                // $pipes now looks like this:
62                // 0 => writeable handle connected to child stdin
63                // 1 => readable handle connected to child stdout
64                // Any error output will be appended to /tmp/error-output.txt
65
66                fwrite($pipes[0], $data[0]);
67                fclose($pipes[0]);
68
69                $output = stream_get_contents($pipes[1]);
70                fclose($pipes[1]);
71
72                // It is important that you close any pipes before calling
73                // proc_close in order to avoid a deadlock
74                $return_value = proc_close($process);
75
76            }
77            $renderer->doc .= $output;
78            return true;
79        } else {
80            return false;
81        }
82    }
83
84    /*
85    function render($mode, &$renderer, $data) {
86        //dbg('function render($mode, &$renderer, $data)-->'.' mode = '.$mode.' data = '.$data);
87        //dbg($data);
88        if ($mode == 'xhtml') {
89            list($state,$match) = $data;
90            switch ($state) {
91                case DOKU_LEXER_ENTER :      break;
92                case DOKU_LEXER_UNMATCHED :
93                    dbglog("In render, $match is:" . $match);
94                    $match = $this->_toc($renderer, $match);
95                    $renderer->doc .= $match;
96                    break;
97                case DOKU_LEXER_EXIT :       break;
98            }
99            return true;
100        }else if ($mode == 'metadata') {
101            //dbg('function render($mode, &$renderer, $data)-->'.' mode = '.$mode.' data = '.$data);
102            //dbg($data);
103            list($state,$match) = $data;
104            switch ($state) {
105                case DOKU_LEXER_ENTER :      break;
106                case DOKU_LEXER_UNMATCHED :
107                    if (!$renderer->meta['title']){
108                        $renderer->meta['title'] = $this->_rst_header($match);
109                    }
110                    $this->_toc($renderer, $match);
111                    $internallinks = $this->_internallinks($match);
112                    #dbg($internallinks);
113                    if (count($internallinks)>0){
114                        foreach($internallinks as $internallink)
115                        {
116                            $renderer->internallink($internallink);
117                        }
118                    }
119                    break;
120                case DOKU_LEXER_EXIT :       break;
121            }
122            return true;
123        } else {
124            return false;
125        }
126    }
127    */
128
129    function _rst_header($text)
130    {
131        $doc = new DOMDocument('1.0','UTF-8');
132        //dbg($doc);
133        $meta = '<!DOCTYPE html><html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head>';
134        $doc->loadHTML($meta.$text);
135        //dbg($doc->saveHTML());
136        if ($nodes = $doc->getElementsByTagName('h1')){
137            return $nodes->item(0)->nodeValue;
138        }
139        return false;
140    }
141
142    function _internallinks($text)
143    {
144        $links = array();
145        if ( ! $text ) return $links;
146        $doc = new DOMDocument('1.0', 'UTF-8');
147        $doc->loadHTML($text);
148        if ($nodes = $doc->getElementsByTagName('a')){
149            foreach($nodes as $atag)
150            {
151                $href = $atag->getAttribute('href');
152                if (!preg_match('/^(https{0,1}:\/\/|ftp:\/\/|mailto:)/i',$href)){
153                    $links[] = $href;
154                }
155            }
156        }
157        return $links;
158    }
159
160    function _toc(&$renderer, $text)
161    {
162        $doc = new DOMDocument('1.0','UTF-8');
163        //dbg($doc);
164        $meta = '<!DOCTYPE html><html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head>';
165        $doc->loadHTML($meta.$text);
166        if ($nodes = $doc->getElementsByTagName("*")){
167            foreach($nodes as $node)
168            {
169                if (preg_match('/h([1-7])/',$node->tagName,$match))
170                {
171                    #dbg($node);
172                    $node->setAttribute('class', 'sectionedit'.$match[1]);
173                    $hid = $renderer->_headerToLink($node->nodeValue,'true');
174                    $node->setAttribute('id',$hid);
175                    $renderer->toc_additem($hid, $node->nodeValue, $match[1]);
176                }
177
178            }
179        }
180        //remove outer tags of content
181        $html = $doc->saveHTML();
182        $html = str_replace('<!DOCTYPE html>','',$html);
183        $html = preg_replace('/.+<body>/', '', $html);
184        $html = preg_replace('@</body>.*</html>@','', $html);
185        return $html;
186    }
187
188}