]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/BlockParser.php
New transform engine sort-of maybe works now.
[SourceForge/phpwiki.git] / lib / BlockParser.php
1 <?php rcs_id('$Id');
2 require_once('lib/HtmlElement.php');
3
4 //FIXME:
5 require_once('lib/transform.php');
6
7 class InlineTransform
8 extends WikiTransform {
9     function InlineTransform() {
10         global $WikiNameRegexp, $AllowedProtocols, $InterWikiLinkRegexp;
11
12         $this->WikiTransform();
13
14         // register functions
15         // functions are applied in order of registering
16
17         $this->register(WT_SIMPLE_MARKUP, 'wtm_plugin_link');
18  
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<>\[\]\"'(),.?]");
25
26         if (function_exists('wtt_interwikilinks')) {
27             $this->register(WT_TOKENIZER, 'wtt_interwikilinks',
28                             pcre_fix_posix_classes("!?(?<![[:alnum:]])") .
29                             "$InterWikiLinkRegexp:[^\\s.,;?()]+");
30         }
31         $this->register(WT_TOKENIZER, 'wtt_bumpylinks', "!?$WikiNameRegexp");
32
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');
36     }
37 };
38
39 function TransformInline ($text) {
40     $lines = preg_split('/[ \t\r]*\n/', trim($text));
41
42     $trfm = new InlineTransform;
43     return new RawXml(rtrim(AsXML($trfm->do_transform('', $lines))));
44 }
45
46 /**
47  * FIXME:
48  *  Still to do:
49  *    headings,
50  *    hr
51  *    tables
52  *    simple li's (vs compound li's) & (dd's).
53  */
54 class BlockParser {
55     function BlockParser ($block_types) {
56         $this->_blockTypes = array();
57         foreach ($block_types as $type)
58           $this->registerBlockType($type);
59     }
60     
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;
67     }
68
69     
70     function parse ($text) {
71         $content = array();
72
73         // strip leading blank lines
74         //$text = preg_replace("/\s*\n/A", "", $text);
75
76         $block = $this->_nextBlock($text);
77         while ($block) {
78             while ($nextBlock = $this->_nextBlock($text)) {
79                 if (!isa($block, "Block_blockquote"))
80                     break;
81                 if (!isa($nextBlock, "Block_blockquote"))
82                     break;
83                 if ($nextBlock->getDepth() >= $block->getDepth())
84                     break;
85
86                 // We have a deeper block quote immediated preceding
87                 // a shallower block-quote.  Merge the two...
88                 $nextBlock->unshiftContent($block);
89                 $block = $nextBlock;
90             }
91
92             $content[] = $block;
93             $block = $nextBlock;
94         }
95         return $content;
96     }
97     
98     function _nextBlock (&$text) {
99         if (preg_match('/\s*\n/A', $text, $m))
100             $text = substr($text, strlen($m[0]));
101         
102         foreach ($this->_blockTypes as $type) {
103             if (($block = $type->Match($text)))
104                 return $block;
105         }
106
107         return false;
108     }
109
110 }
111
112         
113 class Block {
114     /**
115      * (This should be a static member function...)
116      */
117     function Match (&$text) {
118         return $this->_match($text);
119     }
120 }
121
122
123
124 class TightListItem extends HtmlElement
125 {
126     function TightListItem ($tag, $content) {
127         $this->HtmlElement($tag);
128         $this->pushTightContent($content);
129     }
130         
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);
138         }
139     }
140 }
141
142         
143                 
144             
145 class Block_list extends Block
146 {
147     var $_match_re = "/([*#])\s*(?=\S)/A";
148
149     function _match (&$text) {
150         global $BlockParser;
151         
152         if (!(preg_match($this->_match_re, $text, $m)))
153             return false;
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";
158
159         $list = new HtmlElement($m[1] == '*' ? 'ul' : 'ol');
160
161         $was_loose = false;
162         $have_item = preg_match($li_re, $text, $m);
163         assert($have_item);
164         while ($have_item) {
165             $text = substr($text, strlen($m[0]));
166             $body = preg_replace($strip_re, '', $m[0]);
167             
168             $have_item = preg_match($li_re, $text, $m);
169             $is_loose = preg_match($have_item
170                                    ? "/\n\s*\n/"
171                                    : "/\n\s*\n(?!\s*\$)/",
172                                    $body);
173             $tight = !($is_loose ||$was_loose);
174             $was_loose = $is_loose;
175             
176             $li_content = $BlockParser->parse(rtrim($body));
177             if ($tight)
178                 $list->pushContent(new TightListItem('li', $li_content));
179             else
180                 $list->pushContent(new HtmlElement('li', false, $li_content));
181         }
182         assert($list->getContent());
183
184         return $list;
185     }
186 }
187
188 class Block_dl extends Block
189 {
190     var $_re = "/([^\s!].*):\s*\n((?:\ +\S.*(?:\s*\n)?)+)/A";
191
192     function _match (&$text) {
193         
194         $have_item = preg_match($this->_re, $text, $m);
195         if (!$have_item)
196             return false;
197
198         global $BlockParser;
199         $was_loose = false;
200         $list = HTML::dl();
201
202         while ($have_item) {
203             $text = substr($text, strlen($m[0]));
204             $list->pushContent(HTML::dt(rtrim($m[1])));
205             $body = $this->_unindent($m[2]);
206
207             $have_item = preg_match($this->_re, $text, $m);
208             $is_loose = preg_match($have_item
209                                    ? "/\n\s*\n/"
210                                    : "/\n\s*\n(?!\s*\$)/",
211                                    $body);
212             $tight = !($is_loose ||$was_loose);
213             $was_loose = $is_loose;
214
215             $dd_content = $BlockParser->parse(rtrim($body));
216             if ($tight)
217                 $list->pushContent(new TightListItem('dd', $dd_content));
218             else
219                 $list->pushContent(new HtmlElement('dd', false, $dd_content));
220         }
221         return $list->getContent() ? $list : false;
222     }
223
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));
229
230         $ind_re = sprintf("\\ {%d}", $indent);
231         return preg_replace("/^{$ind_re}/m", "", $body);
232     }
233 }
234
235
236 class Block_blockquote extends Block
237 {
238     var $_depth;
239     
240     var $_re = "/(\ +(?=\S)).*(?:\s*\n\\1.*)*\n?/A";
241
242     function _match (&$text) {
243         if (! preg_match($this->_re, $text, $m))
244             return false;
245         $text = substr($text, strlen($m[0]));
246
247         $this->_depth = strlen($m[1]);
248         $pfx = preg_quote($m[1], '/');
249         $body = preg_replace("/^$pfx/m", "", $m[0]);
250         global $BlockParser;
251         return HTML::blockquote(false, $BlockParser->parse($body));
252     }
253
254     function getDepth () {
255         return $this->_depth;
256     }
257 }
258
259 class BlockBlock extends Block
260 {
261     function BlockBlock ($begin_re, $end_re) {
262         $this->_re = ( "/"
263                        . "($begin_re"
264                        . "((?:.|\n)*?)"
265                        . "$end_re)"
266                        . "\s*?\n?"
267                        . "/Ai" );
268     }
269 }
270         
271 class Block_pre extends BlockBlock
272 {
273     function Block_pre () {
274         $this->BlockBlock("<pre>", "(?<!~)<\/pre>");
275     }
276
277     function _match (&$text) {
278         if (!preg_match($this->_re, $text, $m))
279             return false;
280         $text = substr($text, strlen($m[0]));
281
282         return HTML::pre(false, TransformInline($m[2]));
283     }
284
285 }
286
287 class Block_plugin extends BlockBlock
288 {
289     function Block_plugin () {
290         $this->BlockBlock("<\?plugin(?:-form)?\s", "\?>");
291     }
292
293
294     function _match (&$text) {
295         if (!preg_match($this->_re, $text, $m))
296             return false;
297         $text = substr($text, strlen($m[0]));
298
299         global $request;
300         $loader = new WikiPluginLoader;
301
302         return HTML::div(array('class' => 'plugin'),
303                          $loader->expandPI($m[1], $request));
304     }
305 }
306
307 class Block_hr extends Block
308 {
309     var $_re = "/-{4,}\s*?\n?/A";
310     
311     function _match (&$text) {
312         if (!preg_match($this->_re, $text, $m))
313             return false;
314         $text = substr($text, strlen($m[0]));
315         return HTML::hr();
316     }
317 }
318
319 class Block_heading extends Block
320 {
321     var $_re = "/(!{1,3}).*?(\S.*)\n?/A";
322     
323     function _match (&$text) {
324         if (!preg_match($this->_re, $text, $m))
325             return false;
326         $text = substr($text, strlen($m[0]));
327
328         $tag = "h" . (5 - strlen($m[1]));
329         return new HtmlElement($tag, false, rtrim($m[2]));
330     }
331 }
332
333     
334 class Block_p extends Block
335 {
336     var $_re = "/(\S.*(?:\n(?!\s|[*#!]|\<\?|----|<pre>).+)*)\n?/A";
337         
338     function _match (&$text) {
339         if (!preg_match($this->_re, $text, $m))
340             return false;
341         $text = substr($text, strlen($m[0]));
342         
343         return HTML::p(false, TransformInline($m[1]));
344     }
345 }
346
347 $GLOBALS['BlockParser'] = new BlockParser(array('Block_dl',
348                                                 'Block_list',
349                                                 'Block_blockquote',
350                                                 'Block_heading',
351                                                 'Block_hr',
352                                                 'Block_pre',
353                                                 'Block_plugin',
354                                                 'Block_p'));
355
356
357 function NewTransform ($text) {
358     global $BlockParser;
359
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));
364
365     return $BlockParser->parse($text);
366 }
367
368
369 // (c-file-style: "gnu")
370 // Local Variables:
371 // mode: php
372 // tab-width: 8
373 // c-basic-offset: 4
374 // c-hanging-comment-ender-p: nil
375 // indent-tabs-mode: nil
376 // End:   
377 ?>