2 namespace sfjp\Wiki\Processor;
3 use sfjp\Wiki\Formatter;
4 use sfjp\Wiki\Exception;
5 class Trac extends Base {
6 protected $inlinestyle_rules = array(
7 array("bolditalic", "'''''"),
8 array("bold", "'''", ''),
9 array("italic", "(?<!')''"),
10 array("underline", "__"),
11 array("strike", "~~"),
12 array("subscript", ",,"),
13 array("superscript", "\^"),
14 array("monospace", "`"),
17 protected $blockstyle_rules = array(
18 array("list", '^( +)(\*|(?:\d+|[ivx]+|[IVX]+|[a-z]+|[A-Z]+)\.) '),
19 array("escaped", '^ +(!)(\*|(?:\d+|[ivx]+|[IVX]+|[a-z]+|[A-Z]+)\.) '),
20 array("quote", "^(>(?:\s*>)*) "),
21 array("escaped", "^(!)>+ "),
22 array("heading", '^(=+)\s+(.*?)(?:\s+\1)?(?:\s+#(\S+))?\s*\r?$'),
23 array("escaped", '^(!)=+ '),
24 array("define_list", '^(.+?)::\r?$'),
25 array("line", '^----+'),
26 array("escaped", '^(!)----'),
27 array("table", '^\|\|(.*\|\|)+\r?$'),
28 array("escaped", '^(!)\|\|'),
29 array("clear_all", '^\r?$'),
32 protected $uri_rules = array(
33 array('(?:https?|ftp)', '\/\/[^\x00-\x20"<>]+'),
34 array('wiki', '(?:"[^\x00-\x1f\x22\x27\x5b-\x5d\x60:]+"|(?:[a-z0-9-]+:)?[^\x00-\x22\x24-\x2c\x3a-\x40\x5b-\x5e\x60\x7b-\x7e]+)'),
35 array('(?:tracker|ticket)', '\d+'),
36 array('(?:cvs|svn)', '\S+'),
37 array('(?:id|users?)', '[a-zA-Z0-9_-]+'),
38 array('comment', '\d+:\d+:\d+'),
39 array('release', '\S+'),
40 array('isbn', '[A-Za-z0-9-]+'),
41 array('prweb', '[\x21\x23-\x26\x28-\x3b\x3d\x3f-\x5a\x5e-\x7e]+'),
42 array('projects?', '[a-z0-9-]+'),
43 array('mailto', '[!#$%&*+\/=?^_+~0-9A-Za-z.-]+@[0-9A-Za-z.-]+'),
48 protected $_pending_paragraph;
50 protected static $_static_var_cache = array();
52 function __construct($args = null) {
53 parent::__construct($args);
54 $this->formatter = new Formatter\HTML();
55 $this->formatter->setProcessor($this);
56 // TODO: really OK? This makes cyclic reference. PHP don't support weak ref?
57 $this->const_cache_base = "SFJP_WIKIPROCESSOR_CACHE";
58 $this->interwiki_processor = null;
62 public function getCurrentLine() {
63 return $this->current_line;
66 public function getCurrentLineNo() {
67 return $this->current_lineno;
70 protected function getConstCache($name) {
71 $const_name = $this->const_cache_base . "_${name}";
72 return defined($const_name) ? constant($const_name) : null;
75 protected function setConstCache($name, $value) {
76 $const_name = $this->const_cache_base . "_${name}";
77 if (defined($const_name))
78 throw new Exception\InternalError("[BUG] const '$name' was already initialized.");
79 define($const_name, $value);
83 public function getUriRegex($complete = true) {
84 $ret = $this->getConstCache("uri_regex");
86 return $complete ? "/(!)?${ret}/" :$ret;
88 $ret = $this->getConstCache("uri_regex");
91 foreach ($this->uri_rules as $rule) {
92 $regex []= $rule[0].":".$rule[1];
94 $ret = "(?:" . join('|', $regex) . ")";
96 $this->setConstCache("uri_regex", $ret);
97 return $complete ? "/(!)?${ret}/" :$ret;
100 public function getInlinestyleRegex($complete = true) {
101 $ret = $this->getConstCache("inlinestyle_regex");
103 return $complete ? "/(!)?${ret}/" :$ret;
107 array_map(create_function('$a', 'return "($a[1])";'),
108 $this->inlinestyle_rules))
111 $ret = $this->setConstCache("inlinestyle_regex", $regex);
112 return $complete ? "/(!)?${ret}/" :$ret;
115 public function getBlockstyleRules() {
116 return $this->blockstyle_rules;
119 public function reset() {
121 $this->block_stack = array();
122 $this->inline_stack = array();
123 $this->current_lineno = 0;
124 $this->current_line = "";
125 $this->_pending_paragraph = false;
128 public function getInterwikiProcessor() {
129 return $this->interwiki_processor;
132 public function setInterwikiProcessor($f) {
133 $this->interwiki_processor($f);
139 * @param mixed $string
141 * @return return self instance
143 public function process($text = null) {
148 $child_processor = null;
150 $lines = preg_split("/(?<=\n)/", $this->text);
151 foreach ($lines as $line) {
152 $this->current_lineno++;
153 $this->current_line = str_replace("\r", "", $line);
154 if (isset($child_processor)) {
155 if (preg_match('/^\}\}\}/', $line)) {
156 $ret .= $child_processor->process($buf_proc)->getFormattedText();
157 $child_processor->setContext(array("parent_processor" => null));
158 $child_processor = null;
165 if (preg_match('/^\{\{\{(?:\s+(.*))?$/', $line, $match)) {
166 $args = preg_split('/\s+/', trim($match[1]));
167 $name = (count($args) > 0 && $args[0]) ? strtolower(array_shift($args)) : 'auto';
168 $ret .= $this->clear_blockstyle('indent');
169 $ret .= $this->clear_blockstyle('table');
170 $ret .= $this->clear_blockstyle('define');
173 $child_processor = $this->get_plugin_instance("Processor\\$name");
174 } catch (Exception\Plugin_Error $e) {
177 $child_processor = $this->get_plugin_instance("Processor\\Pre");
178 } catch (Exception\Plugin_Error $e) {
179 $ret .= $this->format_plugin_error($orig_error);
184 $child_processor->setContext($this->getContext());
185 $child_processor->setContext(array("parent_processor" => $this));
186 $child_processor->setFormatter($this->getFormatter());
187 $child_processor->setArgs($args);
190 $ret .= $this->parse_line($line);
192 if (isset($child_processor)) {
193 $ret .= $child_processor->process($buf_proc)->getFormattedText();
195 $ret .= $this->clear_inlinestyle();
196 $ret .= $this->clear_blockstyle();
197 if (!$this->hasContext("parent_processor")
198 && !$this->getContext("disable_formatter_cleanup"))
199 $ret .= $this->getFormatter()->cleanup();
200 $this->formatted_text = $ret;
204 protected function run_processor($name, $text) {
205 // TODO: select processor
206 $processor = new Pre();
207 return $processor->process($text);
210 public function parse_line($line) {
215 foreach ($this->blockstyle_rules as $rule) {
218 if (preg_match("/$pat/", $line, $match, PREG_OFFSET_CAPTURE))
221 if (count($match) == 0) {
224 $ret .= $this->clear_inlinestyle();
227 if (is_callable(array(&$this, 'process_block_'.$name))) {
228 $ret .= call_user_func(array(&$this, 'process_block_'.$name), $match, $line);
230 error_log("[BUG] can't process block '$name'.");
231 $ret .= $this->process_block__default($match, $line);
237 protected function process_block_heading($match, $line) {
239 $level = strlen($match[1][0]) + intval($this->getContext('head_excess'));
240 $elem_level = $level <= 6 ? $level : 6;
242 if (array_key_exists(3, $match)) {
243 $headid = trim($match[3][0]);
245 $headid = "h${level}-"
246 . preg_replace_callback($this->getInlinestyleRegex(),
247 create_function('$m', 'return empty($m[1]) ? "" : substr($m[0], 1);'),
251 $c = $this->incrementCounter("id:{$headid}");
252 if ($c > 1) $headid .= "-{$c}";
255 if ($this->getContext('gen_head_id')) {
256 $elem_opt['id'] = str_replace('%', '.', rawurlencode($headid));
259 $ret .= $this->clear_inlinestyle();
260 $ret .= $this->clear_blockstyle();
261 $ret .= $this->formatter->open_element("heading${elem_level}", $elem_opt);
262 $ret .= $this->parse_inline($match[2][0]);
263 $ret .= $this->formatter->close_element("heading{$elem_level}");
267 protected function process_block_list($match, $line) {
268 $ret = $this->clear_blockstyle('indent');
269 $ret .= $this->clear_blockstyle('paragraph');
270 $ret .= $this->clear_blockstyle('table');
271 $ret .= $this->clear_blockstyle('quote');
272 $ret .= $this->clear_blockstyle('define');
273 $level = intval((strlen($match[1][0])+1)/2);
274 $mark = $match[2][0];
275 $mark_types = array('list_mark', 'list_num', 'list_roma', 'list_ROMA', 'list_alpha', 'list_ALPHA');
278 } else if (preg_match('/^\d+\./', $mark)) {
280 } else if (preg_match('/^[ivx]+\./', $mark)) {
282 } else if (preg_match('/^[IVX]+\./', $mark)) {
284 } else if (preg_match('/^[a-z]+\./', $mark)) {
285 $type = "list_alpha";
286 } else if (preg_match('/^[A-Z]+\./', $mark)) {
287 $type = "list_ALPHA";
289 $msg = $this->getFormatter()->raw_node("[BUG] unkown mark '$mark'");
291 $ret = $this->getFormatter()->raw_node('<span class="wiki-system-error">'."$msg</span>");
293 $cur_level = $this->count_in_stack($mark_types, $this->block_stack);
294 if ($level == $cur_level) {
295 $ret .= $this->pop_blockstyle("list_item");
296 } else if ($level > $cur_level) {
298 $ret .= $this->push_blockstyle($type);
299 if ($this->count_in_stack($mark_types, $this->block_stack) >= $level) {
302 $ret .= $this->push_blockstyle("list_item");
305 while ($this->count_in_stack($mark_types, $this->block_stack) > $level) {
306 $ret .= $this->pop_blockstyle("list_item");
309 if ($type != end($this->block_stack)) {
310 $ret .= $this->pop_blockstyle(end($this->block_stack));
311 $ret .= $this->push_blockstyle($type);
313 $ret .= $this->push_blockstyle("list_item");
314 $ret .= $this->parse_inline(substr($line, strlen($match[0][0])));
318 protected function process_block_table($match, $line) {
319 $cells = explode('||', $line);
324 if (!$this->in_stack_p("table"))
325 $ret .= $this->push_blockstyle("table");
327 $ret .= $this->push_blockstyle("table_row");
329 foreach($cells as $c) {
330 $ret .= $this->push_blockstyle("table_col");
331 $ret .= $this->parse_inline($c);
332 $ret .= $this->pop_blockstyle("table_col");
335 $ret .= $this->pop_blockstyle("table_row");
337 $this->last_table_cells = count($cells);
341 protected function process_block_line($match, $line) {
342 return $this->clear_blockstyle()
343 . $this->formatter->open_element("line")
344 . $this->formatter->close_element("line");
347 protected function process_block_quote($match, $line) {
348 $level = count(explode('>', $match[1][0]))-1;
350 $ret .= $this->clear_blockstyle('indent');
351 $ret .= $this->clear_blockstyle('table');
352 $ret .= $this->clear_blockstyle('define');
353 $ret .= $this->clear_blockstyle('list');
354 while ($level < $this->count_in_stack('quote', $this->block_stack)) {
355 $ret .= $this->pop_blockstyle('quote');
357 while ($level > $this->count_in_stack('quote', $this->block_stack)) {
358 if (end($this->block_stack) == 'paragraph') {
359 $ret .= $this->pop_blockstyle('paragraph');
361 $ret .= $this->push_blockstyle('quote');
364 if (end($this->block_stack) != 'paragraph') {
365 $ret .= $this->push_blockstyle("paragraph");
367 $ret .= $this->parse_inline(substr($line, strlen($match[0][0])));
371 protected function process_block_define_list($match, $line) {
372 return $this->getFormatter()->raw_node("<div class=\"wiki-system-error\">def list not implemented yet.</div>"); # TODO:
375 protected function process_block_escaped($match, $line) {
376 $l = substr($line, 0, $match[1][1]) . substr($line, $match[1][1]+1);
377 return $this->process_block__default(null, $l);
380 protected function process_block_clear_all($match, $line) {
381 return $this->clear_blockstyle();
384 protected function process_block__default($match, $line) {
386 $last = end($this->block_stack);
387 $cur_level = $last ? $this->count_in_stack($last, $this->block_stack) : 0;
389 preg_match("/^ +/", $line, $match);
390 $level = count($match) ? intval((strlen($match[0])+1)/2) : 0;
391 if ($last == "list_item" && count($match)) {
393 if ($level >= $cur_level &&
394 $level <= $cur_level + (prev($this->block_stack) == "list_mark" ? 1 : 2)) {
399 if ($level && $last && ($last == "indent" || $last == "list_item") &&
400 $level == $this->count_in_stack($last, $this->block_stack)) {
401 // nop to continue current block element
403 $ret .= $this->clear_blockstyle();
404 for ($i = 0; $level > $i; $i++) {
405 $this->clear_inlinestyle();
406 $ret .= $this->push_blockstyle("indent");
408 } elseif (!$last || $last != "paragraph" ||
409 ($level == 0 && count($this->block_stack) > 1)) {
410 $ret .= $this->clear_blockstyle();
411 $this->_pending_paragraph = true;
414 $ret .= $this->parse_inline(substr($line, count($match) ? strlen($match[0]) : 0));
418 protected function parse_inline($line) {
419 $ret = $this->parse_bracket_and_plugin($line);
420 if ($this->getContext('trac.keep_newline'))
421 $ret .= $this->getFormatter()->open_element('newline');
425 protected function parse_bracket_and_plugin($text) {
429 $cache_name = 'parse_bracket_and_plugin';
430 if (!$regex = $this->getConstCache($cache_name)) {
433 (?:\{\{\{(.*?)\}\}\} # 2: {{{preformatted}}}
434 | \[\[([a-zA-Z_].*?)\]\] # 3: [[plugin()]]
435 | \[(?:wiki:)?\x22([^"]+?)\x22(?:\s+([^\]]+))?\] # 4, 5: ["quoted" lebel]
436 | \[([^\x20\]]+?)(?:\s+([^\]]+))?\] # 6, 7: [link lebel]
437 | wiki:\x22(.+?)\x22 # 8: Quoted WikiName
439 $this->setConstCache($cache_name, $regex);
441 while (preg_match($regex, $text, $match, PREG_OFFSET_CAPTURE)) {
443 if ($match[0][1] > 0) {
444 $fmt_text .= $this->_flush_pending_paragraph();
445 $fmt_text .= $this->parse_inlinestyle(substr($text, 0, $match[0][1]));
447 $text = substr($text, $match[0][1]+ strlen($match[0][0]));
448 if (isset($match[1]) && strlen($match[1][0])) { /* escaped */
449 $fmt_text .= $this->_flush_pending_paragraph();
450 $fmt_text .= $this->formatter->text_node(substr($str, 1));
452 } elseif (isset($match[2]) && strlen($match[2][0])) { /* inline preformatted */
453 $fmt_text .= $this->_flush_pending_paragraph();
454 $fmt_text .= $this->formatter->open_element('monospace');
455 $fmt_text .= $this->formatter->text_node($match[2][0]);
456 $fmt_text .= $this->formatter->close_element('monospace');
457 } elseif (isset($match[3]) && strlen($match[3][0])) { /* plugin */
458 $fmt_text .= $this->process_plugin($match[3][0]);
461 $fmt_text .= $this->_flush_pending_paragraph();
464 if (isset($match[3]) && strlen($match[4][0])) { /* quoted bracket */
465 if (!$this->getContext('disable.link.quoted_bracket')) {
466 $link = $this->gen_uri_link("wiki:\"{$match[4][0]}\"");
467 $label = (isset($match[5]) && strlen($match[5][0])) ? $match[5][0] : $match[4][0];
469 } elseif (isset($match[6]) && strlen($match[6][0])) { /* bracket link */
470 if (!$this->getContext('disable.link.bracket')) {
471 $link = $this->gen_uri_link($match[6][0]);
472 if (!$link && !strrchr($match[6][0], ':')) {
473 // forced as wikiname
474 $link = $this->gen_uri_link("wiki:{$match[6][0]}");
476 $label = (isset($match[7]) && strlen($match[7][0])) ? $match[7][0] : $match[6][0];
478 } elseif (isset($match[8]) && strlen($match[8][0])) { /* quoted wikiname */
479 $link = $this->gen_uri_link($str);
481 $fmt_text .= isset($link) ? $this->create_link((isset($label) ? $label : $str), $link) : $this->formatter->text_node($str);
485 $fmt_text .= $this->_flush_pending_paragraph();
486 $fmt_text .= $this->parse_inlinestyle($text);
490 protected function parse_inlinestyle($text) {
492 $formatted_text = "";
493 while (preg_match($this->getInlinestyleRegex(), $text, $match, PREG_OFFSET_CAPTURE)) {
494 $leading_text = $this->parse_links(substr($text, 0, $match[0][1]));
495 $replace_elem = $this->inlinestyle_callback($match);
496 $formatted_text .= $leading_text . $replace_elem;
497 $text = substr($text, $match[0][1]+ strlen($match[0][0]));
499 $formatted_text .= $this->parse_links($text);
500 return $formatted_text;
503 protected function parse_links($text) {
507 $cache_name = "parse_links";
509 if (!$regex = $this->getConstCache($cache_name)) {
512 (?:\#(\d+) # 2: #nnn tracker
513 | (?<![A-Za-z0-9.#-&-])r(\d+)(?![A-Za-z0-9.#-&-]) # 3: subversion revision
514 | (' . $this->getUriRegex(false) . ') # 4: URI
515 | (?<!\w)(?:[A-Z][a-z0-9]+){2,} # WikiName
517 $this->setConstCache($cache_name, $regex);
520 while (preg_match($regex, $text, $match, PREG_OFFSET_CAPTURE)) {
523 $fmt_text .= $this->formatter->text_node(substr($text, 0, $match[0][1]));
524 $text = substr($text, $match[0][1]+ strlen($match[0][0]));
525 if (isset($match[1]) && strlen($match[1][0])) { /* escaped */
526 $fmt_text .= $this->formatter->text_node(substr($str, 1));
528 } elseif (isset($match[2]) && strlen($match[2][0])) { /* #nnnn tracker */
529 $link = $this->gen_uri_link("ticket:{$match[2][0]}");
530 } elseif (isset($match[3]) && strlen($match[3][0])) { /* SVN */
531 if (!$this->getContext('disable.link.svn_revision')) {
532 $link = $this->getContext('sfjp.svn_rev_base_url') . $match[3][0];
533 $link = array("href" => $link, "class" => "svn");
535 } elseif (isset($match[4]) && strlen($match[4][0])) { /* URI */
536 $link = $this->gen_uri_link($str);
537 } else { /* WikiName */
538 if (!$this->getContext('disable.link.CamelCase')) {
539 $link = $this->getContext('wiki_baseurl');
540 if (substr($link, -1) !== "/")
542 $link .= rawurlencode($str);
547 $fmt_text .= $this->create_link($str, $link);
549 $fmt_text .= $this->formatter->text_node($str);
552 $fmt_text .= $this->formatter->text_node($text);
556 public function gen_uri_link($str) {
558 if (strpos($str, '/') === 0)
559 return array("href" => $str);
560 $part = explode(':', $str, 2);
561 if (count($part) == 1) return null;
564 if ($this->getContext("disable.link.scheme.{$scheme}"))
571 if (!preg_match('!//[^\x00-\x20"<>]+!', $body)) break;
572 $ret = array("href" => $str);
573 if (!($this->getContext("internal_url_regex") &&
574 preg_match("/".str_replace('/', '\/', $this->getContext("internal_url_regex"))."/", $str))) {
575 $ret["class"] = "external";
576 if ($this->getContext('nofollow_on_external_links'))
577 $ret["rel"] = "nofollow";
581 $wiki_allow_chars = '[^:\x00-\x1f\x21-\x23\x27\x5b-\x5d\x60]';
584 if (substr($body, 0, 1) == '"' && substr($body, -1, 1) == '"')
585 $body = substr($body, 1, strlen($body)-2);
586 if (($fragpos = strpos($body, '#')) !== false) {
587 $fragment = substr($body, $fragpos+1);
588 $body = substr($body, 0, $fragpos);
590 if (preg_match("/^([a-z0-9-]+):(${wiki_allow_chars}*)\$/", $body, $m)) { # wiki:group:PageName
591 $ret = $this->getContext('site_root_url')
592 . "/projects/$m[1]/wiki/"
593 . rawurlencode($m[2]);
594 $ret = array("href" => $ret, "class" => "external-wiki");
595 } elseif (preg_match("/^${wiki_allow_chars}+\$/", $body)) {
596 $ret = $this->getContext('wiki_baseurl');
597 if (substr($ret, -1) !== "/")
599 $ret .= rawurlencode($body);
601 if (isset($fragment)) {
602 if (!isset($ret)) $ret = array("href" => "");
603 if (!is_array($ret)) $ret = array("href" => $ret);
604 $ret["href"] .= "#" . Formatter\Base::escape_id_value(str_replace('%', '.', rawurlencode($fragment)));
609 if (preg_match('/^[0-9]+$/', $body)) {
611 $ret = array("href" => $this->getContext('site_root_url').'/ticket/detail.php?id='.$body,
612 "class" => "tracker");
614 $ret = array("href" => $this->getContext('site_root_url')."/tracker.php?id={$body}",
615 "class" => "tracker");
620 if (preg_match('/^[a-z0-9_-]+:/', $body)) {
621 list($group, $path)= explode(':', $body, 2);
623 $group = $this->getContext('sfjp.group_name');
626 if (substr($body, 0, 1) != '/')
628 $ret = $this->getContext('cvs_base_url') . "/${group}${path}";
629 $ret = array("href" => $ret, "class" => "cvs");
632 if (preg_match('/^[a-z0-9_-]+:/', $body)) {
633 list($group, $path)= explode(':', $body, 2);
635 $group = $this->getContext('sfjp.group_name');
638 if (substr($body, 0, 1) != '/')
640 $ret = $this->getContext('sfjp.svn_file_base_url') . $path;
641 $ret = array("href" => $ret, "class" => "svn");
646 if (preg_match('/^[a-z0-9_-]+$/', $body)) {
647 $ret = "/users/" . $body;
648 $ret = array("href" => $ret, "class" => "user");
649 if ($this->getContext('individual_usericon')) {
650 if (empty($ret['style'])) $ret['style'] = '';
651 $ret['style'] .= "background-image: url(".$this->getContext('individual_usericon')."{$body});";
653 if ($this->getContext('override_usericon_size')) {
654 if (empty($ret['style'])) $ret['style'] = '';
655 $ret['style'] .= "padding-left: ".$this->getContext('override_usericon_size')."px;";
660 $parts = explode(':', $body, 3);
661 if (count($parts) == 2) {
662 $ret = $this->getContext('site_root_url')
663 . '/ticket/detail.php?id='.$parts[0].'&cid='.$parts[1];
664 } else if (!$parts[0]) {
665 $ret = $this->getContext('site_root_url')
666 . '/ticket/detail.php?id='.$parts[1].'&cid='.$parts[2];
668 $ret = $this->getContext('site_root_url')
669 . "/ticket/browse.php?group_id=${parts[0]}&tid=${parts[1]}#comment:${body}";
671 $ret = array("href" => $ret, "class" => "ticket");
675 if (preg_match('/^[a-z0-9_-]+$/', $body)) {
676 $ret = $this->getContext('site_root_url')
677 . "/projects/$body/";
678 $ret = array("href" => $ret, "class" => "project");
683 preg_match('/^https?:\/\/([^\/]+)/', $this->getContext('site_root_url'), $m);
684 $site_domain = $m[1];
685 $host = $this->getContext('sfjp.group_name').".${site_domain}";
688 if (preg_match('/^([a-z0-9_-]+):(.*)$/', $body, $m)) { # prweb:project:path
689 $host = "$m[1].${site_domain}";
692 if (substr($path, 0, 1) != "/")
694 $ret = "http://${host}${path}";
695 $ret = array("href" => $ret, "class" => "project-web");
698 $ret = $this->getContext('')
699 . "/projects/" . $body;
700 $ret = array("href" => $ret, "class" => "release");
703 $ret = array("href" => "http://www.amazon.co.jp/gp/product/$body",
704 "class" => "isbnbook", "rel" => "nofollow");
705 if ($aid = $this->getContext('amazon_affiliate_id')) {
706 $ret['href'] .= "?tag={$aid}&linkCode=as2&camp=247&creative=1211&creativeASIN={$body}";
710 $ret = array("href" => "mailto:{$body}", "class" => "mail");
713 if ($this->getInterwikiProcessor() &&
714 is_callable(array($this->getInterwikiProcessor(), 'process'),
716 $ret = $this->getInterwikiProcessor()->process($body);
723 public function create_link($text, $args, $no_escape_html = false) {
724 if (!is_array($args))
725 $args = array("href" => $args);
727 if (array_key_exists('href', $args)) {
728 $args["href"] = $this->encode_url_badchar($args["href"]);
731 $fmt = $this->formatter;
733 $ret .= $fmt->open_element("link", $args);
734 $ret .= $no_escape_html ? $fmt->raw_node($text) : $fmt->text_node($text);
735 $ret .= $fmt->close_element("link");
739 public function encode_url_badchar($str) {
740 return preg_replace_callback('/[^\x21\x23-\x26\x28-\x3b\x3d\x3f-\x5a\x5e-\x7e]+/',
741 create_function('$m', 'return rawurlencode($m[0]);'),
745 public function process_plugin($str) {
747 if (!preg_match('/^([^()]+)(?:\((.*)\))?$/', $str, $match))
750 $args = isset($match[2]) && !empty($match[2]) ? preg_split('/\s*,\s*/', trim($match[2])) : array();
753 $plugin_obj = $this->get_plugin_instance("Plugin\\{$name}");
754 $plug_ret = call_user_func(array($plugin_obj, "process"), $args);
755 if (!$this->is_vary) $this->is_vary = $plugin_obj->is_vary;
756 if ($plugin_obj->is_block) {
757 if ($this->_pending_paragraph) {
758 $this->_pending_paragraph = false;
760 if ($this->in_stack_p('paragraph')) {
761 $plug_ret = $this->pop_blockstyle('paragraph')
762 . $plug_ret . $this->push_blockstyle('paragraph');
765 $plug_ret = $this->_flush_pending_paragraph() . $plug_ret;
768 } catch (Exception\Plugin_Error $e) {
769 return $this->format_plugin_error($e);
773 public function format_plugin_error($e) {
774 if ($this->getContext("suppress_plugin_error")) {
777 return $this->formatter->element('error', "Plugin Error: ".$e->getMessage());
781 public function count_in_stack($name, &$stack) {
782 if (!is_array($name))
783 $name = array($name);
784 $count = array_count_values($stack);
786 foreach ($name as $n) {
787 $ret += array_key_exists($n, $count) ? $count[$n] : 0;
792 public function in_stack_p($name) {
793 return in_array($name, $this->inline_stack) || in_array($name, $this->block_stack);
796 protected function push_blockstyle($name) {
797 $this->block_stack[] = $name;
798 return $this->formatter->open_element($name);
801 protected function pop_blockstyle($name) {
802 return $this->clear_blockstyle($name, 1);
805 protected function clear_inlinestyle($name = null) {
806 return $this->clear_stylestack($this->inline_stack, $name);
809 protected function clear_blockstyle($name = null, $max = null) {
810 $ret = $this->clear_inlinestyle();
811 $ret .= $this->clear_stylestack($this->block_stack, $name, $max);
815 protected function clear_stylestack(&$stack, $name, $max = null) {
818 if (isset($name) && !in_array($name, $stack)) {
819 // return if $name setted and not found in stack.
822 while ($elem = array_pop($stack)) {
823 $ret .= $this->formatter->close_element($elem);
824 if (isset($name) && $name == $elem) {
826 if ($max && $i > $max || !in_array($name, $stack))
833 protected function inlinestyle_callback($match) {
834 $rule = $this->get_matched_rule($match, $this->inlinestyle_rules);
835 if (!isset($rule) && $match[1][0] === "!")
836 return $this->formatter->text_node(substr($match[0][0], 1));
838 if (!in_array($name, $this->inline_stack)) {
839 $this->inline_stack[] = $name;
840 return $this->formatter->open_element($name);
843 while ($name != ($cur_elem = array_pop($this->inline_stack))) {
844 $ret .= $this->formatter->close_element($cur_elem);
846 $ret .= $this->formatter->close_element($name);
851 protected function get_matched_rule($match, $rules, $rule_have_escape = true) {
852 if ($rule_have_escape && strlen($match[1][0])) /* ! escaped */
854 $excess = $rule_have_escape ? 2 : 1;
855 for($i = $excess; isset($match[$i]); $i++) {
856 if ($match[$i][1] >= 0)
859 return $rules[$i - $excess];
862 private function _flush_pending_paragraph() {
863 if (!$this->_pending_paragraph) return '';
864 $this->_pending_paragraph = false;
865 return $this->push_blockstyle("paragraph");