You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
441 lines
13 KiB
441 lines
13 KiB
<?php
|
|
|
|
/**
|
|
* Generic & abstract parser functions & skeleton. It has some functions & generic stuff.
|
|
*
|
|
* @author Josep Arús
|
|
*
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
|
|
* @package mod_wiki
|
|
*/
|
|
|
|
abstract class wiki_markup_parser extends generic_parser {
|
|
|
|
protected $pretty_print = false;
|
|
protected $printable = false;
|
|
|
|
//page id
|
|
protected $wiki_page_id;
|
|
|
|
//sections
|
|
protected $repeated_sections;
|
|
|
|
protected $section_editing = true;
|
|
|
|
//header & ToC
|
|
protected $toc = array();
|
|
protected $maxheaderdepth = 3;
|
|
|
|
/**
|
|
* function wiki_parser_link_callback($link = "")
|
|
*
|
|
* Returns array('content' => "Inside the link", 'url' => "http://url.com/Wiki/Entry", 'new' => false).
|
|
*/
|
|
private $linkgeneratorcallback = array('parser_utils', 'wiki_parser_link_callback');
|
|
private $linkgeneratorcallbackargs = array();
|
|
|
|
/**
|
|
* Table generator callback
|
|
*/
|
|
|
|
private $tablegeneratorcallback = array('parser_utils', 'wiki_parser_table_callback');
|
|
|
|
/**
|
|
* Get real path from relative path
|
|
*/
|
|
private $realpathcallback = array('parser_utils', 'wiki_parser_real_path');
|
|
private $realpathcallbackargs = array();
|
|
|
|
/**
|
|
* Before and after parsing...
|
|
*/
|
|
|
|
protected function before_parsing() {
|
|
$this->toc = array();
|
|
|
|
$this->string = preg_replace('/\r\n/', "\n", $this->string);
|
|
$this->string = preg_replace('/\r/', "\n", $this->string);
|
|
|
|
$this->string .= "\n\n";
|
|
|
|
if (!$this->printable && $this->section_editing) {
|
|
$this->returnvalues['unparsed_text'] = $this->string;
|
|
$this->string = $this->get_repeated_sections($this->string);
|
|
}
|
|
}
|
|
|
|
protected function after_parsing() {
|
|
if (!$this->printable) {
|
|
$this->returnvalues['repeated_sections'] = array_unique($this->returnvalues['repeated_sections']);
|
|
}
|
|
|
|
$this->process_toc();
|
|
|
|
$this->string = preg_replace("/\n\s/", "\n", $this->string);
|
|
$this->string = preg_replace("/\n{2,}/", "\n", $this->string);
|
|
$this->string = trim($this->string);
|
|
$this->string .= "\n";
|
|
}
|
|
|
|
/**
|
|
* Set options
|
|
*/
|
|
|
|
protected function set_options($options) {
|
|
parent::set_options($options);
|
|
|
|
$this->returnvalues['link_count'] = array();
|
|
$this->returnvalues['repeated_sections'] = array();
|
|
$this->returnvalues['toc'] = "";
|
|
|
|
foreach ($options as $name => $o) {
|
|
switch ($name) {
|
|
case 'link_callback':
|
|
$callback = explode(':', $o);
|
|
|
|
global $CFG;
|
|
require_once($CFG->dirroot . $callback[0]);
|
|
|
|
if (function_exists($callback[1])) {
|
|
$this->linkgeneratorcallback = $callback[1];
|
|
}
|
|
break;
|
|
case 'link_callback_args':
|
|
if (is_array($o)) {
|
|
$this->linkgeneratorcallbackargs = $o;
|
|
}
|
|
break;
|
|
case 'real_path_callback':
|
|
$callback = explode(':', $o);
|
|
|
|
global $CFG;
|
|
require_once($CFG->dirroot . $callback[0]);
|
|
|
|
if (function_exists($callback[1])) {
|
|
$this->realpathcallback = $callback[1];
|
|
}
|
|
break;
|
|
case 'real_path_callback_args':
|
|
if (is_array($o)) {
|
|
$this->realpathcallbackargs = $o;
|
|
}
|
|
break;
|
|
case 'table_callback':
|
|
$callback = explode(':', $o);
|
|
|
|
global $CFG;
|
|
require_once($CFG->dirroot . $callback[0]);
|
|
|
|
if (function_exists($callback[1])) {
|
|
$this->tablegeneratorcallback = $callback[1];
|
|
}
|
|
break;
|
|
case 'pretty_print':
|
|
if ($o) {
|
|
$this->pretty_print = true;
|
|
}
|
|
break;
|
|
case 'pageid':
|
|
$this->wiki_page_id = $o;
|
|
break;
|
|
case 'printable':
|
|
if ($o) {
|
|
$this->printable = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generic block rules
|
|
*/
|
|
|
|
protected function line_break_block_rule($match) {
|
|
return '<hr />';
|
|
}
|
|
|
|
protected function list_block_rule($match) {
|
|
preg_match_all("/^\ *([\*\#]{1,5})\ *((?:[^\n]|\n(?!(?:\ *[\*\#])|\n))+)/im", $match[1], $listitems, PREG_SET_ORDER);
|
|
|
|
return $this->process_block_list($listitems) . $match[2];
|
|
}
|
|
|
|
protected function nowiki_block_rule($match) {
|
|
return parser_utils::h('pre', $this->protect($match[1]));
|
|
}
|
|
|
|
/**
|
|
* Generic tag rules
|
|
*/
|
|
|
|
protected function nowiki_tag_rule($match) {
|
|
return parser_utils::h('tt', $this->protect($match[1]));
|
|
}
|
|
|
|
/**
|
|
* Header generation
|
|
*/
|
|
|
|
protected function generate_header($text, $level) {
|
|
$toctext = $text = trim($text);
|
|
|
|
if (!$this->pretty_print && $level == 1) {
|
|
$editlink = '[' . get_string('editsection', 'wiki') . ']';
|
|
$url = array('href' => "edit.php?pageid={$this->wiki_page_id}§ion=" . urlencode($text),
|
|
'class' => 'wiki_edit_section');
|
|
$text .= ' ' . parser_utils::h('a', $this->protect($editlink), $url);
|
|
$toctext .= ' ' . parser_utils::h('a', $editlink, $url);
|
|
}
|
|
|
|
if ($level <= $this->maxheaderdepth) {
|
|
$this->toc[] = array($level, $toctext);
|
|
$num = count($this->toc);
|
|
$text = parser_utils::h('a', "", array('name' => "toc-$num")) . $text;
|
|
}
|
|
|
|
// Display headers as <h3> and lower for accessibility.
|
|
return parser_utils::h('h' . min(6, $level + 2), $text) . "\n\n";
|
|
}
|
|
|
|
/**
|
|
* Table of contents processing after parsing
|
|
*/
|
|
protected function process_toc() {
|
|
if (empty($this->toc)) {
|
|
return;
|
|
}
|
|
|
|
$toc = "";
|
|
$currentsection = array(0, 0, 0);
|
|
$i = 1;
|
|
foreach ($this->toc as & $header) {
|
|
switch ($header[0]) {
|
|
case 1:
|
|
$currentsection = array($currentsection[0] + 1, 0, 0);
|
|
break;
|
|
case 2:
|
|
$currentsection[1]++;
|
|
$currentsection[2] = 0;
|
|
if ($currentsection[0] == 0) {
|
|
$currentsection[0]++;
|
|
}
|
|
break;
|
|
case 3:
|
|
$currentsection[2]++;
|
|
if ($currentsection[1] == 0) {
|
|
$currentsection[1]++;
|
|
}
|
|
if ($currentsection[0] == 0) {
|
|
$currentsection[0]++;
|
|
}
|
|
break;
|
|
default:
|
|
continue 2;
|
|
}
|
|
$number = "$currentsection[0]";
|
|
if (!empty($currentsection[1])) {
|
|
$number .= ".$currentsection[1]";
|
|
if (!empty($currentsection[2])) {
|
|
$number .= ".$currentsection[2]";
|
|
}
|
|
}
|
|
$toc .= parser_utils::h('p', $number . ". " .
|
|
parser_utils::h('a', str_replace(array('[[', ']]'), '', $header[1]), array('href' => "#toc-$i")),
|
|
array('class' => 'wiki-toc-section-' . $header[0] . " wiki-toc-section"));
|
|
$i++;
|
|
}
|
|
|
|
$this->returnvalues['toc'] = "<div class=\"wiki-toc\"><p class=\"wiki-toc-title\">" . get_string('tableofcontents', 'wiki') . "</p>$toc</div>";
|
|
}
|
|
|
|
/**
|
|
* List helpers
|
|
*/
|
|
|
|
private function process_block_list($listitems) {
|
|
$list = array();
|
|
foreach ($listitems as $li) {
|
|
$text = str_replace("\n", "", $li[2]);
|
|
$this->rules($text);
|
|
|
|
if ($li[1][0] == '*') {
|
|
$type = 'ul';
|
|
} else {
|
|
$type = 'ol';
|
|
}
|
|
|
|
$list[] = array(strlen($li[1]), $text, $type);
|
|
}
|
|
$type = $list[0][2];
|
|
return "<$type>" . "\n" . $this->generate_list($list) . "\n" . "</$type>" . "\n";
|
|
}
|
|
|
|
/**
|
|
* List generation function from an array of array(level, text)
|
|
*/
|
|
|
|
protected function generate_list($listitems) {
|
|
$list = "";
|
|
$current_depth = 1;
|
|
$next_depth = 1;
|
|
$liststack = array();
|
|
for ($lc = 0; $lc < count($listitems) && $next_depth; $lc++) {
|
|
$cli = $listitems[$lc];
|
|
$nli = isset($listitems[$lc + 1]) ? $listitems[$lc + 1] : null;
|
|
|
|
$text = $cli[1];
|
|
|
|
$current_depth = $next_depth;
|
|
$next_depth = $nli ? $nli[0] : null;
|
|
|
|
if ($next_depth == $current_depth || $next_depth == null) {
|
|
$list .= parser_utils::h('li', $text) . "\n";
|
|
} else if ($next_depth > $current_depth) {
|
|
$next_depth = $current_depth + 1;
|
|
|
|
$list .= "<li>" . $text . "\n";
|
|
$list .= "<" . $nli[2] . ">" . "\n";
|
|
$liststack[] = $nli[2];
|
|
} else {
|
|
$list .= parser_utils::h('li', $text) . "\n";
|
|
|
|
for ($lv = $next_depth; $lv < $current_depth; $lv++) {
|
|
$type = array_pop($liststack);
|
|
$list .= "</$type>" . "\n" . "</li>" . "\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
for ($lv = 1; $lv < $current_depth; $lv++) {
|
|
$type = array_pop($liststack);
|
|
$list .= "</$type>" . "\n" . "</li>" . "\n";
|
|
}
|
|
|
|
return $list;
|
|
}
|
|
|
|
/**
|
|
* Table generation functions
|
|
*/
|
|
|
|
protected function generate_table($table) {
|
|
$table_html = call_user_func_array($this->tablegeneratorcallback, array($table));
|
|
|
|
return $table_html;
|
|
}
|
|
|
|
protected function format_image($src, $alt, $caption = "", $align = 'left') {
|
|
$src = $this->real_path($src);
|
|
return parser_utils::h('div', parser_utils::h('p', $caption) . '<img src="' . $src . '" alt="' . $alt . '" />', array('class' => 'wiki_image_' . $align));
|
|
}
|
|
|
|
protected function real_path($url) {
|
|
$callbackargs = array_merge(array($url), $this->realpathcallbackargs);
|
|
return call_user_func_array($this->realpathcallback, $callbackargs);
|
|
}
|
|
|
|
/**
|
|
* Link internal callback
|
|
*/
|
|
|
|
protected function link($link, $anchor = "") {
|
|
$link = trim($link);
|
|
if (preg_match("/^(https?|s?ftp):\/\/.+$/i", $link)) {
|
|
$link = trim($link, ",.?!");
|
|
return array('content' => $link, 'url' => $link);
|
|
} else {
|
|
$callbackargs = $this->linkgeneratorcallbackargs;
|
|
$callbackargs['anchor'] = $anchor;
|
|
$link = call_user_func_array($this->linkgeneratorcallback, array($link, $callbackargs));
|
|
|
|
if (isset($link['link_info'])) {
|
|
$l = $link['link_info']['link'];
|
|
unset($link['link_info']['link']);
|
|
$this->returnvalues['link_count'][$l] = $link['link_info'];
|
|
}
|
|
return $link;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Format links
|
|
*/
|
|
|
|
protected function format_link($text) {
|
|
$matches = array();
|
|
if (preg_match("/^([^\|]+)\|(.+)$/i", $text, $matches)) {
|
|
$link = $matches[1];
|
|
$content = trim($matches[2]);
|
|
if (preg_match("/(.+)#(.*)/is", $link, $matches)) {
|
|
$link = $this->link($matches[1], $matches[2]);
|
|
} else if ($link[0] == '#') {
|
|
$link = array('url' => "#" . urlencode(substr($link, 1)));
|
|
} else {
|
|
$link = $this->link($link);
|
|
}
|
|
|
|
$link['content'] = $content;
|
|
} else {
|
|
$link = $this->link($text);
|
|
}
|
|
|
|
if (isset($link['new']) && $link['new']) {
|
|
$options = array('class' => 'wiki_newentry');
|
|
} else {
|
|
$options = array();
|
|
}
|
|
|
|
$link['content'] = $this->protect($link['content']);
|
|
$link['url'] = $this->protect($link['url']);
|
|
|
|
$options['href'] = $link['url'];
|
|
|
|
if ($this->printable) {
|
|
$options['href'] = '#'; //no target for the link
|
|
}
|
|
return array($link['content'], $options);
|
|
}
|
|
|
|
/**
|
|
* Section editing
|
|
*/
|
|
|
|
public function get_section($header, $text, $clean = false) {
|
|
if ($clean) {
|
|
$text = preg_replace('/\r\n/', "\n", $text);
|
|
$text = preg_replace('/\r/', "\n", $text);
|
|
$text .= "\n\n";
|
|
}
|
|
|
|
$regex = "/(.*?)(=\ *".preg_quote($header, '/')."\ *=*\n.*?)((?:\n=[^=]+.*)|$)/is";
|
|
preg_match($regex, $text, $match);
|
|
|
|
if (!empty($match)) {
|
|
return array($match[1], $match[2], $match[3]);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
protected function get_repeated_sections(&$text, $repeated = array()) {
|
|
$this->repeated_sections = $repeated;
|
|
return preg_replace_callback($this->blockrules['header']['expression'], array($this, 'get_repeated_sections_callback'), $text);
|
|
}
|
|
|
|
protected function get_repeated_sections_callback($match) {
|
|
$num = strlen($match[1]);
|
|
$text = trim($match[2]);
|
|
if ($num == 1) {
|
|
if (in_array($text, $this->repeated_sections)) {
|
|
$this->returnvalues['repeated_sections'][] = $text;
|
|
return $text . "\n";
|
|
} else {
|
|
$this->repeated_sections[] = $text;
|
|
}
|
|
}
|
|
|
|
return $match[0];
|
|
}
|
|
|
|
}
|
|
|