1<?php
2/**
3 * DokuWiki Plugin syntaxhighlighter4 (Syntax Component).
4 *
5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6 * @author  CrazyMax <contact@crazymax.dev>
7 */
8
9// must be run within Dokuwiki
10if (!defined('DOKU_INC')) {
11  die();
12}
13
14if (!defined('DOKU_LF')) {
15  define('DOKU_LF', "\n");
16}
17if (!defined('DOKU_TAB')) {
18  define('DOKU_TAB', "\t");
19}
20if (!defined('DOKU_PLUGIN')) {
21  define('DOKU_PLUGIN', DOKU_INC.'lib/plugins/');
22}
23
24require_once DOKU_PLUGIN.'syntax.php';
25
26class syntax_plugin_syntaxhighlighter4 extends DokuWiki_Syntax_Plugin {
27
28  protected $syntax;
29
30  /**
31   * @return string Syntax mode type
32   */
33  public function getType() {
34      return 'protected';
35  }
36
37  /**
38   * @return string Paragraph type
39   */
40  public function getPType() {
41      return 'block';
42  }
43
44  /**
45   * @return int Sort order - Low numbers go before high numbers
46   */
47  public function getSort() {
48      return 195;
49  }
50
51  /**
52   * Connect lookup pattern to lexer.
53   *
54   * @param string $mode Parser mode
55   */
56  public function connectTo($mode) {
57      $this->Lexer->addEntryPattern('<sxh(?=[^\r\n]*?>.*?</sxh>)', $mode, 'plugin_syntaxhighlighter4');
58      $tags = explode(',', $this->getConf('override'));
59      foreach ($tags as $tag) {
60        $this->Lexer->addEntryPattern('<' . $tag . '(?=[^\r\n]*?>.*?</' . $tag . '>)', $mode, 'plugin_syntaxhighlighter4');
61      }
62  }
63
64  public function postConnect() {
65      $this->Lexer->addExitPattern('</sxh>', 'plugin_syntaxhighlighter4');
66      $tags = explode(',', $this->getConf('override'));
67      foreach ($tags as $tag) {
68        $this->Lexer->addExitPattern('</' . $tag . '>', 'plugin_syntaxhighlighter4');
69      }
70  }
71
72  /**
73   * Handle matches of the syntaxhighlighter4 syntax.
74   *
75   * @param string        $match   The match of the syntax
76   * @param int           $state   The state of the handler
77   * @param int           $pos   The position in the document
78   * @param Doku_Handler  $handler The handler
79   *
80   * @return array Data for the renderer
81   */
82  public function handle($match, $state, $pos, Doku_Handler $handler) {
83      switch ($state) {
84        case DOKU_LEXER_ENTER:
85          $this->syntax = strtolower(substr($match, 1));
86          return false;
87        case DOKU_LEXER_UNMATCHED:
88          // will include everything from <sxh ... to ... </sxh>
89          list($attr, $content) = preg_split('/>/u', $match, 2);
90          if ($this->isSyntaxOk()) {
91              $attr = trim($attr);
92              if ($attr == null) {
93                // No brush and no options, use "text" with default options.
94                $attr = 'text';
95              } elseif (substr($attr, 0, 1) == ';') {
96                // Options but no brush, add "text" brush.
97                $attr = 'text'.$attr;
98              }
99          } else {
100              $attr = null;
101          }
102          return array($this->syntax, $attr, $content);
103      }
104      return false;
105  }
106
107  /**
108   * Render xhtml output or metadata.
109   *
110   * @param string        $mode   Renderer mode (supported modes: xhtml)
111   * @param Doku_Renderer $renderer The renderer
112   * @param array         $data   The data from the handler() function
113   *
114   * @return bool If rendering was successful.
115   */
116  public function render($mode, Doku_Renderer $renderer, $data) {
117      if ($mode != 'xhtml') {
118        return false;
119      }
120
121      if (count($data) != 3) {
122        return true;
123      }
124
125      list($this->syntax, $attr, $content) = $data;
126      if ($this->isSyntaxOk()) {
127        $title = $this->procTitle($attr);
128        $highlight = $this->procHighlight($attr);
129        $renderer->doc .= '<pre class="brush: '.strtolower($attr.$highlight).'"'.$title.'>'.$renderer->_xmlEntities($content).'</pre>';
130      } else {
131        $renderer->file($content);
132      }
133
134      return true;
135  }
136
137  private function procTitle($attr) {
138      if ($this->syntax == 'file') {
139        $title = trim(substr($attr, strpos($attr, ' ') + 1));
140        if (!empty($title)) {
141          return ' title="' . $title . '"';
142        }
143      } elseif (preg_match('/title:/i', $attr)) {
144        // Extract title(s) from $attr string.
145        $attr_array = explode(';', $attr);
146        $title_array = preg_grep('/title:/i', $attr_array);
147        // Extract everything BUT title(s) from $attr string.
148        $not_title_array = preg_grep('/title:/i', $attr_array, PREG_GREP_INVERT);
149        $attr = implode(';', $not_title_array);
150        // If there are multiple titles, use the last one.
151        $title = array_pop($title_array);
152        return ' title="'.preg_replace("/.*title:\s{0,}(.*)/i", '$1', $title).'"';
153      }
154
155      return '';
156  }
157
158  private function procHighlight($attr) {
159      $highlight = '';
160
161      // Check highlight attr for lines ranges
162      if (preg_match('/highlight:/i', $attr, $matches)) {
163        // Extract highlight from $attr string.
164        $attr_array = explode(';', $attr);
165        $highlight_array = preg_grep('/highlight:/i', $attr_array);
166        // Extract everything BUT highlight from $attr string.
167        $not_highlight_array = preg_grep('/highlight:/i', $attr_array, PREG_GREP_INVERT);
168        $attr = implode(';', $not_highlight_array);
169        // If there are multiple hightlights, use the last one.
170        $highlight_str = array_pop($highlight_array);
171        $highlight_str = preg_replace("/.*highlight:\s{0,}(.*)/i", '$1', $highlight_str);
172        // Remove [ ]
173        $highlight_str = str_replace(array('[', ']'), '', $highlight_str);
174        // Process ranges if exists
175        $highlight_exp = explode(',', $highlight_str);
176        foreach ($highlight_exp as $highlight_elt) {
177          if (!empty($highlight)) {
178              $highlight .= ',';
179          }
180          $highlight_elt = trim($highlight_elt);
181          $highlight_elt_exp = explode('-', $highlight_elt);
182          if (count($highlight_elt_exp) == 2) {
183              foreach (range($highlight_elt_exp[0], $highlight_elt_exp[1]) as $key => $lineNumber) {
184                if ($key > 0) {
185                  $highlight .= ',';
186                }
187                $highlight .= $lineNumber;
188              }
189          } else {
190              $highlight .= $highlight_elt;
191          }
192        }
193        $highlight = ' highlight: ['.$highlight.']';
194      }
195
196      return $highlight;
197  }
198
199  private function isSyntaxOk() {
200      if ($this->syntax == 'sxh') {
201        return true;
202      }
203      $tags = explode(',', $this->getConf('override'));
204      foreach ($tags as $tag) {
205        if ($this->syntax == $tag) {
206          return true;
207        }
208      }
209      return false;
210  }
211}
212