4 * Generic & abstract parser functions & skeleton. It has some functions & generic stuff.
8 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
12 abstract class wiki_markup_parser extends generic_parser {
14 protected $pretty_print = false;
15 protected $printable = false;
18 protected $wiki_page_id;
21 protected $repeated_sections;
23 protected $section_editing = true;
26 protected $toc = array();
27 protected $maxheaderdepth = 3;
30 * function wiki_parser_link_callback($link = "")
32 * Returns array('content' => "Inside the link", 'url' => "http://url.com/Wiki/Entry", 'new' => false).
34 private $linkgeneratorcallback = array('parser_utils', 'wiki_parser_link_callback');
35 private $linkgeneratorcallbackargs = array();
38 * Table generator callback
41 private $tablegeneratorcallback = array('parser_utils', 'wiki_parser_table_callback');
44 * Get real path from relative path
46 private $realpathcallback = array('parser_utils', 'wiki_parser_real_path');
47 private $realpathcallbackargs = array();
50 * Before and after parsing...
53 protected function before_parsing() {
56 $this->string = preg_replace('/\r\n/', "\n", $this->string);
57 $this->string = preg_replace('/\r/', "\n", $this->string);
59 $this->string .= "\n\n";
61 if (!$this->printable && $this->section_editing) {
62 $this->returnvalues['unparsed_text'] = $this->string;
63 $this->string = $this->get_repeated_sections($this->string);
67 protected function after_parsing() {
68 if (!$this->printable) {
69 $this->returnvalues['repeated_sections'] = array_unique($this->returnvalues['repeated_sections']);
74 $this->string = preg_replace("/\n\s/", "\n", $this->string);
75 $this->string = preg_replace("/\n{2,}/", "\n", $this->string);
76 $this->string = trim($this->string);
77 $this->string .= "\n";
84 protected function set_options($options) {
85 parent::set_options($options);
87 $this->returnvalues['link_count'] = array();
88 $this->returnvalues['repeated_sections'] = array();
89 $this->returnvalues['toc'] = "";
91 foreach ($options as $name => $o) {
94 $callback = explode(':', $o);
97 require_once($CFG->dirroot . $callback[0]);
99 if (function_exists($callback[1])) {
100 $this->linkgeneratorcallback = $callback[1];
103 case 'link_callback_args':
105 $this->linkgeneratorcallbackargs = $o;
108 case 'real_path_callback':
109 $callback = explode(':', $o);
112 require_once($CFG->dirroot . $callback[0]);
114 if (function_exists($callback[1])) {
115 $this->realpathcallback = $callback[1];
118 case 'real_path_callback_args':
120 $this->realpathcallbackargs = $o;
123 case 'table_callback':
124 $callback = explode(':', $o);
127 require_once($CFG->dirroot . $callback[0]);
129 if (function_exists($callback[1])) {
130 $this->tablegeneratorcallback = $callback[1];
135 $this->pretty_print = true;
139 $this->wiki_page_id = $o;
143 $this->printable = true;
151 * Generic block rules
154 protected function line_break_block_rule($match) {
158 protected function list_block_rule($match) {
159 preg_match_all("/^\ *([\*\#]{1,5})\ *((?:[^\n]|\n(?!(?:\ *[\*\#])|\n))+)/im", $match[1], $listitems, PREG_SET_ORDER);
161 return $this->process_block_list($listitems) . $match[2];
164 protected function nowiki_block_rule($match) {
165 return parser_utils::h('pre', $this->protect($match[1]));
172 protected function nowiki_tag_rule($match) {
173 return parser_utils::h('tt', $this->protect($match[1]));
180 protected function generate_header($text, $level) {
181 $toctext = $text = trim($text);
183 if (!$this->pretty_print && $level == 1) {
184 $editlink = '[' . get_string('editsection', 'wiki') . ']';
185 $url = array('href' => "edit.php?pageid={$this->wiki_page_id}§ion=" . urlencode($text),
186 'class' => 'wiki_edit_section');
187 $text .= ' ' . parser_utils::h('a', $this->protect($editlink), $url);
188 $toctext .= ' ' . parser_utils::h('a', $editlink, $url);
191 if ($level <= $this->maxheaderdepth) {
192 $this->toc[] = array($level, $toctext);
193 $num = count($this->toc);
194 $text = parser_utils::h('a', "", array('name' => "toc-$num")) . $text;
197 // Display headers as <h3> and lower for accessibility.
198 return parser_utils::h('h' . min(6, $level + 2), $text) . "\n\n";
202 * Table of contents processing after parsing
204 protected function process_toc() {
205 if (empty($this->toc)) {
210 $currentsection = array(0, 0, 0);
212 foreach ($this->toc as & $header) {
213 switch ($header[0]) {
215 $currentsection = array($currentsection[0] + 1, 0, 0);
218 $currentsection[1]++;
219 $currentsection[2] = 0;
220 if ($currentsection[0] == 0) {
221 $currentsection[0]++;
225 $currentsection[2]++;
226 if ($currentsection[1] == 0) {
227 $currentsection[1]++;
229 if ($currentsection[0] == 0) {
230 $currentsection[0]++;
236 $number = "$currentsection[0]";
237 if (!empty($currentsection[1])) {
238 $number .= ".$currentsection[1]";
239 if (!empty($currentsection[2])) {
240 $number .= ".$currentsection[2]";
243 $toc .= parser_utils::h('p', $number . ". " .
244 parser_utils::h('a', str_replace(array('[[', ']]'), '', $header[1]), array('href' => "#toc-$i")),
245 array('class' => 'wiki-toc-section-' . $header[0] . " wiki-toc-section"));
249 $this->returnvalues['toc'] = "<div class=\"wiki-toc\"><p class=\"wiki-toc-title\">" . get_string('tableofcontents', 'wiki') . "</p>$toc</div>";
256 private function process_block_list($listitems) {
258 foreach ($listitems as $li) {
259 $text = str_replace("\n", "", $li[2]);
262 if ($li[1][0] == '*') {
268 $list[] = array(strlen($li[1]), $text, $type);
271 return "<$type>" . "\n" . $this->generate_list($list) . "\n" . "</$type>" . "\n";
275 * List generation function from an array of array(level, text)
278 protected function generate_list($listitems) {
282 $liststack = array();
283 for ($lc = 0; $lc < count($listitems) && $next_depth; $lc++) {
284 $cli = $listitems[$lc];
285 $nli = isset($listitems[$lc + 1]) ? $listitems[$lc + 1] : null;
289 $current_depth = $next_depth;
290 $next_depth = $nli ? $nli[0] : null;
292 if ($next_depth == $current_depth || $next_depth == null) {
293 $list .= parser_utils::h('li', $text) . "\n";
294 } else if ($next_depth > $current_depth) {
295 $next_depth = $current_depth + 1;
297 $list .= "<li>" . $text . "\n";
298 $list .= "<" . $nli[2] . ">" . "\n";
299 $liststack[] = $nli[2];
301 $list .= parser_utils::h('li', $text) . "\n";
303 for ($lv = $next_depth; $lv < $current_depth; $lv++) {
304 $type = array_pop($liststack);
305 $list .= "</$type>" . "\n" . "</li>" . "\n";
310 for ($lv = 1; $lv < $current_depth; $lv++) {
311 $type = array_pop($liststack);
312 $list .= "</$type>" . "\n" . "</li>" . "\n";
319 * Table generation functions
322 protected function generate_table($table) {
323 $table_html = call_user_func_array($this->tablegeneratorcallback, array($table));
328 protected function format_image($src, $alt, $caption = "", $align = 'left') {
329 $src = $this->real_path($src);
330 return parser_utils::h('div', parser_utils::h('p', $caption) . '<img src="' . $src . '" alt="' . $alt . '" />', array('class' => 'wiki_image_' . $align));
333 protected function real_path($url) {
334 $callbackargs = array_merge(array($url), $this->realpathcallbackargs);
335 return call_user_func_array($this->realpathcallback, $callbackargs);
339 * Link internal callback
342 protected function link($link, $anchor = "") {
344 if (preg_match("/^(https?|s?ftp):\/\/.+$/i", $link)) {
345 $link = trim($link, ",.?!");
346 return array('content' => $link, 'url' => $link);
348 $callbackargs = $this->linkgeneratorcallbackargs;
349 $callbackargs['anchor'] = $anchor;
350 $link = call_user_func_array($this->linkgeneratorcallback, array($link, $callbackargs));
352 if (isset($link['link_info'])) {
353 $l = $link['link_info']['link'];
354 unset($link['link_info']['link']);
355 $this->returnvalues['link_count'][$l] = $link['link_info'];
365 protected function format_link($text) {
367 if (preg_match("/^([^\|]+)\|(.+)$/i", $text, $matches)) {
369 $content = trim($matches[2]);
370 if (preg_match("/(.+)#(.*)/is", $link, $matches)) {
371 $link = $this->link($matches[1], $matches[2]);
372 } else if ($link[0] == '#') {
373 $link = array('url' => "#" . urlencode(substr($link, 1)));
375 $link = $this->link($link);
378 $link['content'] = $content;
380 $link = $this->link($text);
383 if (isset($link['new']) && $link['new']) {
384 $options = array('class' => 'wiki_newentry');
389 $link['content'] = $this->protect($link['content']);
390 $link['url'] = $this->protect($link['url']);
392 $options['href'] = $link['url'];
394 if ($this->printable) {
395 $options['href'] = '#'; //no target for the link
397 return array($link['content'], $options);
404 public function get_section($header, $text, $clean = false) {
406 $text = preg_replace('/\r\n/', "\n", $text);
407 $text = preg_replace('/\r/', "\n", $text);
411 $regex = "/(.*?)(=\ *".preg_quote($header, '/')."\ *=*\n.*?)((?:\n=[^=]+.*)|$)/is";
412 preg_match($regex, $text, $match);
414 if (!empty($match)) {
415 return array($match[1], $match[2], $match[3]);
421 protected function get_repeated_sections(&$text, $repeated = array()) {
422 $this->repeated_sections = $repeated;
423 return preg_replace_callback($this->blockrules['header']['expression'], array($this, 'get_repeated_sections_callback'), $text);
426 protected function get_repeated_sections_callback($match) {
427 $num = strlen($match[1]);
428 $text = trim($match[2]);
430 if (in_array($text, $this->repeated_sections)) {
431 $this->returnvalues['repeated_sections'][] = $text;
434 $this->repeated_sections[] = $text;