]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/BlockParser.php
Start of new parser. It doesn't work yet.
[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 $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     function parse ($text) {
70         $content = array();
71
72         // strip leading blank lines
73         $text = preg_replace("/\s*\n/A", "", $text);
74
75         while ($text) {
76             $parsed = $this->_parseOne($text);
77             assert (!is_array($parsed));
78             $content[] = $parsed;
79
80             // strip blank lines
81             $text = preg_replace("/\s*\n/A", "", $text);
82         }
83         return $content;
84     }
85
86     function _parseOne (&$text) {
87         $block = $this->_grokBlockType($text); // determine block type from first line of text
88         assert($block);
89         $btext = $block->extractBlock($text);
90
91         while ($nextBlock = $this->_grokBlockType($text)) {
92             $next_text = $nextBlock->extractBlock($text);
93             
94             if (! $nextBlock->matches($btext . $next_text)) {
95                 // Can't combine blocks.
96                 $text = $next_text . $text;
97                 if ($nextBlock->getDepth() < $block->getDepth()) {
98                     //if (!isa($block, "Block_blockquote")) {
99                     if ($block->_tag != 'blockquote') {
100                         // FIXME: move this.
101                         $block = new Block_blockquote;
102                         assert(preg_match('/(?:\s*\n)?( *)(?=\S)/', $btext, $m));
103                         $block->_init($m);
104                     }
105                 }
106                 break;
107             }
108             assert ($nextBlock->getDepth() <= $block->getDepth());
109             $block = $nextBlock;
110             $btext .= $next_text;
111         }
112
113         if (0) {
114             echo "BLOCK $block->_tag:<pre>\n";
115             echo htmlspecialchars($btext);
116             echo "\n</pre><br />\n";
117         }
118         
119         return $block->parse($btext);
120     }
121
122     function _grokBlockType ($text) {
123         foreach ($this->_blockTypes as $type) {
124             if (($block = $type->Match($text)))
125                 return $block;
126         }
127         return false;
128     }
129
130 }
131
132         
133 class Block {
134     var $_match_re;
135     var $_prefix_re;
136     var $_block_re;
137     var $_depth = 0;
138     var $_tag;
139     var $_attr = false;
140
141     /*
142     function Block ($match = false) {
143         if ($match)
144             $this->_init($match);
145     }
146     */
147
148     function _init ($match) {
149         $qprefix = preg_quote($match[1], '/');
150         $this->_prefix_re = $qprefix;
151         $this->_block_re = "(?:(?:\s*\n)?${qprefix}.*\n?)+";
152         $this->_depth = strlen($match[1]);
153     }
154
155     /**
156      * (This should be a static member function...)
157      */
158     function Match ($text) {
159         if (! preg_match($this->_match_re, $text, $match))
160             return false;
161
162         $block = $this;        // Copy self.
163         $block->_init($match);
164         return $block;
165     }
166         
167     function extractBlock (&$text) {
168         $block_re = &$this->_block_re;
169         assert(preg_match("/$block_re/Ami", $text, $m));
170         $text = substr($text, strlen($m[0]));
171         return $m[0];
172     }
173
174     function getDepth () {
175         return $this->_depth;
176     }
177
178     function matches ($text) {
179         $block_re = &$this->_block_re;
180         return preg_match("/$block_re\$/Ai", $text);
181     }
182     
183     function parse ($text) {
184         assert ($this->matches($text));
185
186         // Strip block prefix from $text.
187         $prefix = &$this->_prefix_re;
188         $text = preg_replace("/^$prefix/m", "", $text);
189         
190
191         global $BlockParser;
192         return $this->wrap($BlockParser->parse($text));
193     }
194
195     // FIXME: rename
196     function wrap($content) {
197         // DEBUGGING:
198         //if (is_array($content)) $content = join('', $content);
199         //$content = preg_replace("/(?<!\n)$/", "\n", $content);
200         //return "$this->_tag:\n" . preg_replace("/^(?!\$)/m", "  ", $content);
201         
202         return new HtmlElement($this->_tag, $this->_attr, $content);
203     }
204     
205 }
206
207 class ListBlock extends Block
208 {
209     var $_match_re = "/(?:\s*\n)?( *[*#] *(?=\S))/A";
210     
211     /**
212      * Get a regexp which matches the line prefix for
213      * any <li> of the same type (ul/ol) and depth.
214      */
215     function makeLiPrefixRegexp ($match) {
216         return preg_quote($match[1], '/');
217     }
218     
219     /**
220      * Get a regexp which matches the line prefix for
221      * any <li> continuation lines.
222      */
223     function makeContPrefixRegexp ($match) {
224         return sprintf(" {%d}", strlen($match[1]));
225     }
226 }
227
228 class Block_list extends ListBlock
229 {
230     function _init ($match) {
231         $this->_tag = $this->grokListType($match);
232         
233         $liprefix = $this->makeLiPrefixRegexp($match);
234         $cprefix = $this->makeContPrefixRegexp($match);
235
236         preg_match('/^ */', $match[1], $m);
237         $this->_depth = strlen($m[0]);
238         
239         $this->_prefix_re = false; // don't strip any prefix
240         $this->_block_re = ( "(?:\s*\n)?"          // leading blank lines.
241                              . "(?:"
242                              . "${liprefix}.*\n?" // first line
243                              . "(?:(?:\s*\n)?${cprefix}.*\n?)*" // continuation lines
244                              . ")+" );
245         
246     }
247
248     function grokListType ($match) {
249         return preg_match("/#\s*\$/", $match[0]) ? 'ol' : 'ul';
250     }
251
252     function parse ($text) {
253         global $ListParser;
254         return $this->wrap($ListParser->parse($text));
255     }
256 }
257
258
259 class Block_li extends ListBlock
260 {
261     var $_tag = 'li';
262
263     function _init ($match) {
264         $liprefix = $this->makeLiPrefixRegexp($match);
265         $cprefix = $this->makeContPrefixRegexp($match);
266
267         $this->_prefix_re = "(?:${cprefix}|${liprefix})";
268         $this->_block_re = ( "${liprefix}.*\n?" // first line
269                              . "(?:(?:\s*\n)?${cprefix}.*\n?)*" ); // continuation lines
270     }
271 }
272
273 class Block_dl extends Block
274 {
275     var $_tag = 'dl';
276     var $_match_re = "/(?:\s*\n)?[^\s*#].*:\s*\n +\S/A";
277     var $_prefix_re = false;    // no prefix to strip
278     var $_block_re = "(?:(?:\s*\n)?[^\s*#].*:(?:\s*\n +\S.*)+\n?)+";
279
280     function _init () {
281     }
282
283     function parse ($text) {
284         $dt = new Block_dt;
285         $dd = new Block_dd;
286         
287         $content = array();
288         while ($block = $dt->Match($text)) {
289             $btext = $block->extractBlock($text);
290             $content[] = $block->parse($btext);
291             
292             $block = $dd->Match($text);
293             assert($block);
294             $btext = $block->extractBlock($text);
295             $content[] = $block->parse($btext);
296         }
297         assert(preg_match("/\s*\$/A", $text));
298         return $this->wrap($content);
299     }
300 }
301
302 class Block_dt extends Block
303 {
304     var $_tag = 'dt';
305     var $_match_re = "/(?:\s*\n)?[^\s*#].*:\s*?\n/A";
306     var $_block_re = "(?:\s*\n)?[^\s*#].*:\s*\n";
307
308     function _init () {
309     }
310     
311     function parse ($text) {
312         
313         assert(preg_match("/(\S.*?)\s*:/A", $text, $m));
314         return $this->wrap($m[1]);
315     }
316 }
317
318
319 class Block_dd extends Block
320 {
321     var $_tag = 'dd';
322     var $_match_re = "/(?:(?:\s*\n)? +\S.*\n?)+/A";
323     var $_block_re = "(?:(?:\s*\n)? +\S.*\n?)+";
324
325     function _init ($match) {
326         $indent = $this->_getIndent($match[0]);
327         $this->_prefix_re =  sprintf(" {%d}", $indent);
328     }
329
330     function _getIndent ($body) {
331         assert(preg_match_all("/^ +(?=\S)/m", $body, $m));
332         $indent = strlen($m[0][0]);
333         foreach ($m[0] as $pfx)
334             $indent = min($indent, strlen($pfx));
335         return $indent;
336     }
337 }
338
339
340 class Block_blockquote extends Block
341 {
342     var $_tag = 'blockquote';
343     var $_match_re = "/(?:\s*\n)?( +(?=\S))/A";
344 }
345
346 class BlockBlock extends Block
347 {
348     var $_prefix_re = false;    // no prefix to strip
349
350     function BlockBlock ($begin_re, $end_re) {
351         $this->_begin_re = $begin_re;
352         $this->_end_re = $end_re;
353         
354         $this->_block_re = ( "(?:\s*\n)?"
355                              . $begin_re
356                              . "(?:.|\n)*?"
357                              . $end_re
358                              . "\s*?(?=\n|\S|$)" );
359         $this->_match_re = "/" . $this->_block_re . "/Ai";
360     }
361
362     function _init () {
363     }
364
365     function _strip ($text) {
366         $beg = $this->_begin_re;
367         $end = $this->_end_re;
368         
369         $text = preg_replace("/.*?${beg}/Asi", "", $text);
370         $text = preg_replace("/${end}.*?$/si", "", $text);
371         return $text;
372     }
373
374     function parse ($text) {
375         // FIXME: parse inline markup.
376         return $this->wrap(TransformInline($this->_strip($text)));
377     }
378 }
379         
380     
381     
382 class Block_pre extends BlockBlock
383 {
384     var $_tag = 'pre';
385
386     function Block_pre () {
387         $this->BlockBlock("<pre>", "(?<!~)<\/pre>");
388     }
389 }
390
391 class Block_plugin extends BlockBlock
392 {
393     var $_tag = 'div';
394     
395     function Block_plugin () {
396         $this->BlockBlock("<\?plugin(?:-form)?\s", "\?>");
397         $this->_attr = array('class' => 'plugin');
398     }
399
400     function parse ($text) {
401         global $request;
402         $loader = new WikiPluginLoader;
403         return $this->wrap($loader->expandPI($text, $request));
404     }
405 }
406
407 class Block_p extends Block
408 {
409     var $_tag = 'p';
410     var $_match_re = "/(?=\S)/A";
411     var $_prefix_re = false;    // no prefix to strip
412     var $_block_re = "\S.*\n?(?:^(?!\<\?)[^\s*#].*\n?)*";
413         
414     function _init ($match) {
415     }
416
417     function parse ($text) {
418         // FIXME: parse inline markup.
419         return $this->wrap(TransformInline($text));
420     }
421 }
422
423 $GLOBALS['BlockParser'] = new BlockParser(array('Block_dl',
424                                                 'Block_list',
425                                                 'Block_blockquote',
426                                                 'Block_pre',
427                                                 'Block_plugin',
428                                                 'Block_p'));
429
430 $GLOBALS['ListParser'] = new BlockParser(array('Block_li'));
431
432
433 // (c-file-style: "gnu")
434 // Local Variables:
435 // mode: php
436 // tab-width: 8
437 // c-basic-offset: 4
438 // c-hanging-comment-ender-p: nil
439 // indent-tabs-mode: nil
440 // End:   
441 ?>