2 require_once('lib/HtmlElement.php');
5 require_once('lib/transform.php');
8 extends WikiTransform {
9 function InlineTransform() {
10 global $WikiNameRegexp, $AllowedProtocols, $InterWikiLinkRegexp;
12 $this->WikiTransform();
15 // functions are applied in order of registering
17 $this->register(WT_SIMPLE_MARKUP, 'wtm_plugin_link');
19 $this->register(WT_TOKENIZER, 'wtt_doublebrackets', '\[\[');
20 //$this->register(WT_TOKENIZER, 'wtt_footnotes', '^\[\d+\]');
21 //$this->register(WT_TOKENIZER, 'wtt_footnoterefs', '\[\d+\]');
22 $this->register(WT_TOKENIZER, 'wtt_bracketlinks', '\[.+?\]');
23 $this->register(WT_TOKENIZER, 'wtt_urls',
24 "!?\b($AllowedProtocols):[^\s<>\[\]\"'()]*[^\s<>\[\]\"'(),.?]");
26 if (function_exists('wtt_interwikilinks')) {
27 $this->register(WT_TOKENIZER, 'wtt_interwikilinks',
28 pcre_fix_posix_classes("!?(?<![[:alnum:]])") .
29 "$InterWikiLinkRegexp:[^\\s.,;?()]+");
31 $this->register(WT_TOKENIZER, 'wtt_bumpylinks', "!?$WikiNameRegexp");
33 $this->register(WT_SIMPLE_MARKUP, 'wtm_htmlchars');
34 $this->register(WT_SIMPLE_MARKUP, 'wtm_linebreak');
35 $this->register(WT_SIMPLE_MARKUP, 'wtm_bold_italics');
39 function TransformInline ($text) {
40 $lines = preg_split('/[ \t\r]*\n/', trim($text));
42 $trfm = new InlineTransform;
43 return new RawXml(rtrim(AsXML($trfm->do_transform('', $lines))));
52 * simple li's (vs compound li's) & (dd's).
55 function BlockParser ($block_types) {
56 $this->_blockTypes = array();
57 foreach ($block_types as $type)
58 $this->registerBlockType($type);
61 function registerBlockType ($class) {
62 // FIXME: this is hackish.
63 // It's because there seems to be no way to
64 // call static members of $class.
65 $prototype = new $class (false);
66 $this->_blockTypes[] = $prototype;
70 function parse ($text) {
73 // strip leading blank lines
74 //$text = preg_replace("/\s*\n/A", "", $text);
76 $block = $this->_nextBlock($text);
78 while ($nextBlock = $this->_nextBlock($text)) {
79 if (!isa($block, "Block_blockquote"))
81 if (!isa($nextBlock, "Block_blockquote"))
83 if ($nextBlock->getDepth() >= $block->getDepth())
86 // We have a deeper block quote immediated preceding
87 // a shallower block-quote. Merge the two...
88 $nextBlock->unshiftContent($block);
98 function _nextBlock (&$text) {
99 if (preg_match('/\s*\n/A', $text, $m))
100 $text = substr($text, strlen($m[0]));
102 foreach ($this->_blockTypes as $type) {
103 if (($block = $type->Match($text)))
115 * (This should be a static member function...)
117 function Match (&$text) {
118 return $this->_match($text);
124 class TightListItem extends HtmlElement
126 function TightListItem ($tag, $content) {
127 $this->HtmlElement($tag);
128 $this->pushTightContent($content);
131 function pushTightContent ($content) {
132 if (!is_array($content))
133 $content = array($content);
134 foreach ($content as $c) {
135 if (isa($c, "HtmlElement") && $c->getTag() == 'p')
136 $c = $c->getContent();
137 $this->pushContent($c);
145 class Block_list extends Block
147 var $_match_re = "/([*#])\s*(?=\S)/A";
149 function _match (&$text) {
152 if (!(preg_match($this->_match_re, $text, $m)))
154 $li_pfx = preg_quote($m[0], '/');
155 $c_pfx = sprintf("\\ {%d}", strlen($m[0]));
156 $li_re = "/${li_pfx}\S.*(?:\s*\n${c_pfx}.*)*(?:\s*\n)?/A";
157 $strip_re = "/^(?:${li_pfx}|${c_pfx})/m";
159 $list = new HtmlElement($m[1] == '*' ? 'ul' : 'ol');
162 $have_item = preg_match($li_re, $text, $m);
165 $text = substr($text, strlen($m[0]));
166 $body = preg_replace($strip_re, '', $m[0]);
168 $have_item = preg_match($li_re, $text, $m);
169 $is_loose = preg_match($have_item
171 : "/\n\s*\n(?!\s*\$)/",
173 $tight = !($is_loose ||$was_loose);
174 $was_loose = $is_loose;
176 $li_content = $BlockParser->parse(rtrim($body));
178 $list->pushContent(new TightListItem('li', $li_content));
180 $list->pushContent(new HtmlElement('li', false, $li_content));
182 assert($list->getContent());
188 class Block_dl extends Block
190 var $_re = "/([^\s!].*):\s*\n((?:\ +\S.*(?:\s*\n)?)+)/A";
192 function _match (&$text) {
194 $have_item = preg_match($this->_re, $text, $m);
203 $text = substr($text, strlen($m[0]));
204 $list->pushContent(HTML::dt(rtrim($m[1])));
205 $body = $this->_unindent($m[2]);
207 $have_item = preg_match($this->_re, $text, $m);
208 $is_loose = preg_match($have_item
210 : "/\n\s*\n(?!\s*\$)/",
212 $tight = !($is_loose ||$was_loose);
213 $was_loose = $is_loose;
215 $dd_content = $BlockParser->parse(rtrim($body));
217 $list->pushContent(new TightListItem('dd', $dd_content));
219 $list->pushContent(new HtmlElement('dd', false, $dd_content));
221 return $list->getContent() ? $list : false;
224 function _unindent ($body) {
225 assert(preg_match_all("/^ +(?=\S)/m", $body, $m));
226 $indent = strlen($m[0][0]);
227 foreach ($m[0] as $pfx)
228 $indent = min($indent, strlen($pfx));
230 $ind_re = sprintf("\\ {%d}", $indent);
231 return preg_replace("/^{$ind_re}/m", "", $body);
236 class Block_blockquote extends Block
240 var $_re = "/(\ +(?=\S)).*(?:\s*\n\\1.*)*\n?/A";
242 function _match (&$text) {
243 if (! preg_match($this->_re, $text, $m))
245 $text = substr($text, strlen($m[0]));
247 $this->_depth = strlen($m[1]);
248 $pfx = preg_quote($m[1], '/');
249 $body = preg_replace("/^$pfx/m", "", $m[0]);
251 return HTML::blockquote(false, $BlockParser->parse($body));
254 function getDepth () {
255 return $this->_depth;
259 class BlockBlock extends Block
261 function BlockBlock ($begin_re, $end_re) {
271 class Block_pre extends BlockBlock
273 function Block_pre () {
274 $this->BlockBlock("<pre>", "(?<!~)<\/pre>");
277 function _match (&$text) {
278 if (!preg_match($this->_re, $text, $m))
280 $text = substr($text, strlen($m[0]));
282 return HTML::pre(false, TransformInline($m[2]));
287 class Block_plugin extends BlockBlock
289 function Block_plugin () {
290 $this->BlockBlock("<\?plugin(?:-form)?\s", "\?>");
294 function _match (&$text) {
295 if (!preg_match($this->_re, $text, $m))
297 $text = substr($text, strlen($m[0]));
300 $loader = new WikiPluginLoader;
302 return HTML::div(array('class' => 'plugin'),
303 $loader->expandPI($m[1], $request));
307 class Block_hr extends Block
309 var $_re = "/-{4,}\s*?\n?/A";
311 function _match (&$text) {
312 if (!preg_match($this->_re, $text, $m))
314 $text = substr($text, strlen($m[0]));
319 class Block_heading extends Block
321 var $_re = "/(!{1,3}).*?(\S.*)\n?/A";
323 function _match (&$text) {
324 if (!preg_match($this->_re, $text, $m))
326 $text = substr($text, strlen($m[0]));
328 $tag = "h" . (5 - strlen($m[1]));
329 return new HtmlElement($tag, false, rtrim($m[2]));
334 class Block_p extends Block
336 var $_re = "/(\S.*(?:\n(?!\s|[*#!]|\<\?|----|<pre>).+)*)\n?/A";
338 function _match (&$text) {
339 if (!preg_match($this->_re, $text, $m))
341 $text = substr($text, strlen($m[0]));
343 return HTML::p(false, TransformInline($m[1]));
347 $GLOBALS['BlockParser'] = new BlockParser(array('Block_dl',
357 function NewTransform ($text) {
360 // Expand leading tabs.
361 // FIXME: do this better.
362 $text = preg_replace('/^\ *[^\ \S\n][^\S\n]*/me', "str_repeat(' ', strlen('\\0'))", $text);
363 assert(!preg_match('/^\ *\t/', $text));
365 return $BlockParser->parse($text);
369 // (c-file-style: "gnu")
374 // c-hanging-comment-ender-p: nil
375 // indent-tabs-mode: nil