]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/BlockParser.php
Transform API cleanup.
[SourceForge/phpwiki.git] / lib / BlockParser.php
1 <?php rcs_id('$Id: BlockParser.php,v 1.22 2002-02-08 03:01:11 dairiki Exp $');
2 /* Copyright (C) 2002, Geoffrey T. Dairiki <dairiki@dairiki.org>
3  *
4  * This file is part of PhpWiki.
5  * 
6  * PhpWiki is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  * 
11  * PhpWiki is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License
17  * along with PhpWiki; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20 require_once('lib/HtmlElement.php');
21 require_once('lib/InlineParser.php');
22
23 ////////////////////////////////////////////////////////////////
24 //
25 //
26
27 /**
28  * FIXME:
29  *  Still to do:
30  *    (old-style) tables
31  */
32
33 // FIXME: unify this with the RegexpSet in InlinePArser.
34
35 /**
36  * Return type from RegexpSet::match and RegexpSet::nextMatch.
37  *
38  * @see RegexpSet
39  */
40 class AnchoredRegexpSet_match {
41     /**
42      * The matched text.
43      */
44     var $match;
45
46     /**
47      * The text following the matched text.
48      */
49     var $postmatch;
50
51     /**
52      * Index of the regular expression which matched.
53      */
54     var $regexp_ind;
55 }
56
57 /**
58  * A set of regular expressions.
59  *
60  * This class is probably only useful for InlineTransformer.
61  */
62 class AnchoredRegexpSet
63 {
64     /** Constructor
65      *
66      * @param $regexps array A list of regular expressions.  The
67      * regular expressions should not include any sub-pattern groups
68      * "(...)".  (Anonymous groups, like "(?:...)", as well as
69      * look-ahead and look-behind assertions are fine.)
70      */
71     function AnchoredRegexpSet ($regexps) {
72         $this->_regexps = $regexps;
73         $this->_re = "/((" . join(")|(", $regexps) . "))/Ax";
74     }
75
76     /**
77      * Search text for the next matching regexp from the Regexp Set.
78      *
79      * @param $text string The text to search.
80      *
81      * @return object  A RegexpSet_match object, or false if no match.
82      */
83     function match ($text) {
84         if (! preg_match($this->_re, $text, $m)) {
85             return false;
86         }
87         
88         $match = new AnchoredRegexpSet_match;
89         $match->postmatch = substr($text, strlen($m[0]));
90         $match->match = $m[1];
91         $match->regexp_ind = count($m) - 3;
92         return $match;
93     }
94
95     /**
96      * Search for next matching regexp.
97      *
98      * Here, 'next' has two meanings:
99      *
100      * Match the next regexp(s) in the set, at the same position as the last match.
101      *
102      * If that fails, match the whole RegexpSet, starting after the position of the
103      * previous match.
104      *
105      * @param $text string Text to search.
106      *
107      * @param $prevMatch A RegexpSet_match object
108      *
109      * $prevMatch should be a match object obtained by a previous
110      * match upon the same value of $text.
111      *
112      * @return object  A RegexpSet_match object, or false if no match.
113      */
114     function nextMatch ($text, $prevMatch) {
115         // Try to find match at same position.
116         $regexps = array_slice($this->_regexps, $prevMatch->regexp_ind + 1);
117         if (!$regexps) {
118             return false;
119         }
120
121         $pat= "/ ( (" . join(')|(', $regexps) . ") ) /Axs";
122
123         if (! preg_match($pat, $text, $m)) {
124             return false;
125         }
126         
127         $match = new AnchoredRegexpSet_match;
128         $match->postmatch = substr($text, strlen($m[0]));
129         $match->match = $m[1];
130         $match->regexp_ind = count($m) - 3 + $prevMatch->regexp_ind + 1;;
131         return $match;
132     }
133 }
134
135
136     
137 class BlockParser_Input {
138
139     function BlockParser_Input ($text) {
140         
141         // Expand leading tabs.
142         // FIXME: do this better.
143         //
144         // We want to ensure the only characters matching \s are ' ' and "\n".
145         //
146         $text = preg_replace('/(?![ \n])\s/', ' ', $text);
147         assert(!preg_match('/(?![ \n])\s/', $text));
148
149         $this->_lines = preg_split('/[^\S\n]*\n/', $text);
150         $this->_pos = 0;
151     }
152
153     function currentLine () {
154         if ($this->_pos >= count($this->_lines))
155             return false;
156         return $this->_lines[$this->_pos];
157     }
158         
159     function nextLine () {
160         $this->_pos++;
161         if ($this->_pos >= count($this->_lines))
162             return false;
163         return $this->_lines[$this->_pos];
164     }
165
166     function advance () {
167         $this->_pos++;
168     }
169     
170     function getPos () {
171         return $this->_pos;
172     }
173
174     function setPos ($pos) {
175         $this->_pos = $pos;
176     }
177
178     function getPrefix () {
179         return '';
180     }
181
182     function getDepth () {
183         return 0;
184     }
185
186     function where () {
187         if ($this->_pos < count($this->_lines))
188             return $this->_lines[$this->_pos];
189         else
190             return "<EOF>";
191     }
192
193
194     
195     function _debug ($tab, $msg) {
196         //return ;
197         $where = $this->where();
198         $tab = str_repeat('____', $this->getDepth() ) . $tab;
199         printXML(HTML::div("$tab $msg: at: '",
200                            HTML::tt($where),
201                            "'"));
202     }
203 }
204
205 class BlockParser_InputSubBlock extends BlockParser_Input
206 {
207     function BlockParser_InputSubBlock (&$input, $prefix_re, $initial_prefix = false) {
208         $this->_input = &$input;
209         $this->_prefix_pat = "/$prefix_re|\\s*\$/Ax";
210         $this->_prefix = $initial_prefix;
211
212         // FIXME: hackish
213         $this->_block_types = &$input->_block_types;
214         $this->_regexpset = &$input->_regexpset;
215     }
216
217     function currentLine () {
218         if (($line = $this->_input->currentLine()) === false)
219             return false;
220         if ($this->_prefix === false) {
221             if (!preg_match($this->_prefix_pat, $line, $m))
222                 return false;
223             $this->_prefix = $m[0];
224         }
225         return (string) substr($line, strlen($this->_prefix));
226     }
227         
228     function nextLine () {
229         if (($line = $this->_input->nextLine()) === false) {
230             $this->_prefix = false;
231             return false;
232         }
233         if (!preg_match($this->_prefix_pat, $line, $m)) {
234             $this->_prefix = false;
235             return false;
236         }
237         $this->_prefix = $m[0];
238         return (string) substr($line, strlen($this->_prefix));
239     }
240
241     function advance () {
242         $this->_prefix = false;
243         $this->_input->advance();
244     }
245     
246     function getPos () {
247         return array($this->_prefix, $this->_input->getPos());
248     }
249
250     function setPos ($pos) {
251         $this->_prefix = $pos[0];
252         $this->_input->setPos($pos[1]);
253     }
254     
255     function getPrefix () {
256         assert ($this->_prefix !== false);
257         return $this->_input->getPrefix() . $this->_prefix;
258     }
259
260     function getDepth () {
261         return $this->_input->getDepth() + 1;
262     }
263
264     function where () {
265         return $this->_input->where();
266     }
267 }
268     
269
270
271 class Tightenable extends HtmlElement {
272     var $_isTight = false;
273
274     function Tightenable ($tag /*, ...*/) {
275         $this->_init(func_get_args());
276     }
277     
278     function tighten () {
279         if (! $this->_isTight) {
280             $content = &$this->_content;
281             for ($i = 0; $i < count($content); $i++) {
282                 if (!isa($content[$i], 'Tightenable'))
283                     continue;
284                 $content[$i]->tighten();
285             }
286             $this->_isTight = true;
287         }
288     }
289
290     function canTighten () {
291         $content = &$this->_content;
292         for ($i = 0; $i < count($content); $i++) {
293             if (!isa($content[$i], 'Tightenable'))
294                 continue;
295             if (!$content[$i]->canTighten())
296                 return false;
297         }
298         return true;
299     }
300 }
301
302 class TightenableParagraph extends Tightenable {
303     function TightenableParagraph (/*...*/) {
304         $this->_init('p');
305         $this->pushContent(func_get_args());
306     }
307
308     function tighten () {
309         $this->_isTight = true;
310     }
311
312     function canTighten () {
313         return true;
314     }
315
316     function printXML () {
317         if ($this->_isTight)
318             return XmlContent::printXML();
319         else
320             return parent::printXML();
321     }
322
323     function asXML () {
324         if ($this->_isTight)
325             return XmlContent::asXML();
326         else
327             return parent::asXML();
328     }
329 }
330
331 class ParsedBlock extends Tightenable {
332     var $_isLoose = false;
333     
334     function ParsedBlock (&$input, $tag = 'div', $attr = false) {
335         $this->Tightenable($tag, $attr);
336         $this->_initBlockTypes();
337         $this->_parse($input);
338     }
339
340     function canTighten () {
341         if ($this->_isLoose)
342             return false;
343         return parent::canTighten();
344     }
345     
346     function _parse (&$input) {
347         for ($block = $this->_getBlock($input); $block; $block = $nextBlock) {
348             while ($nextBlock = $this->_getBlock($input)) {
349                 // Attempt to merge current with following block.
350                 if (! ($merged = $block->merge($nextBlock, $this->_atSpace)) ) {
351                     break;      // can't merge
352                 }
353                 $block = $merged;
354             }
355             $this->pushContent($block->finish());
356         }
357     }
358
359     // FIXME: hackish
360     function _initBlockTypes () {
361         foreach (array('oldlists', 'list', 'dl', 'table_dl',
362                        'blockquote', 'heading', 'hr', 'pre', 'email_blockquote',
363                        'plugin', 'p')
364                  as $type) {
365             $class = "Block_$type";
366             $proto = new $class;
367             $this->_block_types[] = $proto;
368             $this->_regexps[] = $proto->_re;
369         }
370         $this->_regexpset = new AnchoredRegexpSet($this->_regexps);
371     }
372
373     function _getLine (&$input) {
374         $line = $input->currentLine();
375         if ( $line || $line === false) {
376             $this->_atSpace = false;
377             return $line;
378         }
379         
380         $pos = $input->getPos();
381         while ( ($line = $input->nextLine()) !== false ) {
382             if ($line) {
383                 $this->_atSpace = true;
384                 $this->_isLoose = true;
385                 return $line;
386             }
387         }
388         // EOF.
389         $input->setPos($pos);
390         return false;
391     }
392         
393     function _getBlock (&$input) {
394         if (! ($line = $this->_getLine($input)) )
395             return false;
396
397         $re_set = &$this->_regexpset;
398         for ($m = $re_set->match($line); $m; $m = $re_set->nextMatch($line, $m)) {
399             $block = $this->_block_types[$m->regexp_ind];
400             //$this->_debug('>', get_class($block));
401             
402             if ($block->_match($input, $m)) {
403                 //$this->_debug('<', get_class($block));
404                 //$block->_follows_space = $this->_atSpace;
405                 return $block;
406             }
407             //$this->_debug('[', "_match failed");
408         }
409
410
411         //if ($input->getDepth() == 0) {
412         // We should never get here.
413         //preg_match('/.*/A', substr($this->_text, $this->_pos), $m);// get first line
414         trigger_error("Couldn't match block: '$line'", E_USER_NOTICE);
415         //}
416         
417         return false;
418     }
419 }
420
421 class WikiText extends ParsedBlock {
422     function WikiText ($text) {
423         $input = new BlockParser_Input($text);
424         $this->ParsedBlock($input);
425     }
426 }
427
428 class SubBlock extends ParsedBlock {
429     function SubBlock (&$input, $indent_re, $initial_indent = false,
430                        $tag = 'div', $attr = false) {
431         $subinput = new BlockParser_InputSubBlock($input, $indent_re, $initial_indent);
432         $this->ParsedBlock($subinput, $tag, $attr);
433     }
434 }
435
436 class BlockMarkup {
437     var $_re;
438
439         
440     function _match (&$input, $match) {
441         trigger_error('pure virtual', E_USER_ERROR);
442     }
443
444     function merge ($followingBlock, $followsSpace) {
445         return false;
446     }
447
448     function finish () {
449         trigger_error('pure virtual', E_USER_ERROR);
450     }
451 }
452
453 class Block_blockquote extends BlockMarkup
454 {
455     var $_depth;
456
457     var $_re = '\ +(?=\S)';
458     
459     function _match (&$input, $m) {
460         $this->_depth = strlen($m->match);
461         $indent = sprintf("\\ {%d}", $this->_depth);
462         $this->_block = new SubBlock($input, $indent, $m->match,
463                                      'blockquote');
464         return true;
465     }
466
467     function merge ($nextBlock, $followsSpace) {
468         if (get_class($nextBlock) == get_class($this)) {
469             assert ($nextBlock->_depth < $this->_depth);
470             $nextBlock->_block->unshiftContent($this->_block);
471             return $nextBlock;
472         }
473         return false;
474     }
475
476     function finish () {
477         return $this->_block;
478     }
479 }
480
481 class Block_list extends BlockMarkup
482 {
483     //var $_tag = 'ol' or 'ul';
484     var $_re = '\ {0,4}
485                 (?: [+#] | -(?!-) | [o](?=\ )
486                   | [*] (?! \S[^*]*(?<=\S)[*](?!\S) )
487                 )\ *(?=\S)';
488
489     var $_isLoose = false;
490     var $_content = array();
491
492     function _match (&$input, $m) {
493         // A list as the first content in a list is not allowed.
494         // E.g.:
495         //   *  * Item
496         // Should markup as <ul><li>* Item</li></ul>,
497         // not <ul><li><ul><li>Item</li></ul>/li></ul>.
498         //
499         if (preg_match('/[*#+-o]/', $input->getPrefix())) {
500             return false;
501         }
502         
503         $prefix = $m->match;
504         $indent = sprintf("\\ {%d}", strlen($prefix));
505
506         $bullet = trim($m->match);
507         $this->_tag = $bullet == '#' ? 'ol' : 'ul';
508
509         $this->_content[] = new SubBlock($input, $indent, $m->match, 'li');
510         return true;
511     }
512     
513     function merge ($nextBlock, $followsSpace) {
514         if (isa($nextBlock, 'Block_list') && $this->_tag == $nextBlock->_tag) {
515             array_splice($this->_content, count($this->_content), 0,
516                          $nextBlock->_content);
517             if ($followsSpace)
518                 $this->_isLoose = true;
519             return $this;
520         }
521         return false;
522     }
523
524     function finish () {
525         $list = new Tightenable($this->_tag, false, $this->_content);
526         if (!$this->_isLoose && $list->canTighten())
527             $list->tighten();
528         return $list;
529     }
530 }
531
532 class Block_dl extends Block_list
533 {
534     var $_tag = 'dl';
535     var $_re = '\ {0,4}\S.*:\s*$';
536
537     function _match (&$input, $m) {
538         if (!($p = $this->_do_match($input, $m)))
539             return false;
540         list ($term, $defn) = $p;
541         
542         $this->_content[] = HTML::dt($term);
543         $this->_content[] = $defn;
544         return true;
545     }
546
547     function _do_match (&$input, $m) {
548         $pos = $input->getPos();
549
550         $firstIndent = strspn($m->match, ' ');
551         $pat = sprintf('/\ {%d,%d}(?=\s*\S)/A', $firstIndent + 1, $firstIndent + 5);
552
553         while ( ($line = $input->nextLine()) !== false ) {
554             if (preg_match($pat, $line, $mm)) {
555                 $indent = strlen($mm[0]);
556                 break;
557             }
558         }
559         $input->setPos($pos);
560         
561         if (!isset($indent))
562             return false;       // No body found.
563
564         $input->advance();      // Skip the first line.
565
566         $term = TransformInline(rtrim(substr(trim($m->match),0,-1)));
567         $defn = new SubBlock($input, sprintf("\\ {%d}", $indent), false, 'dd');
568         return array($term, $defn);
569     }
570 }
571
572
573
574 class Block_table_dl_defn extends XmlContent
575 {
576     var $nrows;
577     var $ncols;
578     
579     function Block_table_dl_defn ($term, $defn) {
580         $this->XmlContent();
581         if (!is_array($defn))
582             $defn = $defn->getContent();
583
584         $this->_ncols = $this->_ComputeNcols($defn);
585         
586         $this->_nrows = 0;
587         foreach ($defn as $item) {
588             if ($this->_IsASubtable($item))
589                 $this->_addSubtable($item);
590             else
591                 $this->_addToRow($item);
592         }
593         $this->_flushRow();
594
595         $th = HTML::th($term);
596         if ($this->_nrows > 1)
597             $th->setAttr('rowspan', $this->_nrows);
598         $this->_setTerm($th);
599     }
600
601     function _addToRow ($item) {
602         if (empty($this->_accum)) {
603             $this->_accum = HTML::td();
604             if ($this->_ncols > 2)
605                 $this->_accum->setAttr('colspan', $this->_ncols - 1);
606         }
607         $this->_accum->pushContent($item);
608     }
609
610     function _flushRow () {
611         if (!empty($this->_accum)) {
612             $this->pushContent(HTML::tr($this->_accum));
613             $this->_accum = false;
614             $this->_nrows++;
615         }
616     }
617
618     function _addSubtable ($table) {
619         $this->_flushRow();
620         foreach ($table->getContent() as $subdef) {
621             $this->pushContent($subdef);
622             $this->_nrows += $subdef->nrows();
623         }
624     }
625
626     function _setTerm ($th) {
627         $first_row = &$this->_content[0];
628         if (isa($first_row, 'Block_table_dl_defn'))
629             $first_row->_setTerm($th);
630         else
631             $first_row->unshiftContent($th);
632     }
633     
634     function _ComputeNcols ($defn) {
635         $ncols = 2;
636         foreach ($defn as $item) {
637             if ($this->_IsASubtable($item)) {
638                 $row = $this->_FirstDefn($item);
639                 $ncols = max($ncols, $row->ncols() + 1);
640             }
641         }
642         return $ncols;
643     }
644
645     function _IsASubtable ($item) {
646         return isa($item, 'HtmlElement')
647             && $item->getTag() == 'table'
648             && $item->getAttr('class') == 'wiki-dl-table';
649     }
650
651     function _FirstDefn ($subtable) {
652         $defs = $subtable->getContent();
653         return $defs[0];
654     }
655
656     function ncols () {
657         return $this->_ncols;
658     }
659
660     function nrows () {
661         return $this->_nrows;
662     }
663
664     function setWidth ($ncols) {
665         assert($ncols >= $this->_ncols);
666         if ($ncols <= $this->_ncols)
667             return;
668         $rows = &$this->_content;
669         for ($i = 0; $i < count($rows); $i++) {
670             $row = &$rows[$i];
671             if (isa($row, 'Block_table_dl_defn'))
672                 $row->setWidth($ncols - 1);
673             else {
674                 $n = count($row->_content);
675                 $lastcol = &$row->_content[$n - 1];
676                 $lastcol->setAttr('colspan', $ncols - 1);
677             }
678         }
679     }
680 }
681
682 class Block_table_dl extends Block_dl
683 {
684     var $_tag = 'table';
685     var $_attr = array('class' => 'wiki-dl-table',
686                        'border' => 2, // FIXME: CSS?
687                        'cellspacing' => 0,
688                        'cellpadding' => 6);
689     
690
691     var $_re = '\ {0,4} (?:\S.*)? \| \s* $';
692
693     function _match (&$input, $m) {
694         if (!($p = $this->_do_match($input, $m)))
695             return false;
696         list ($term, $defn) = $p;
697
698         $this->_content[] = new Block_table_dl_defn($term, $defn);
699         return true;
700     }
701             
702     function finish () {
703
704         $defs = &$this->_content;
705
706         $ncols = 0;
707         foreach ($defs as $defn)
708             $ncols = max($ncols, $defn->ncols());
709         foreach ($defs as $key => $defn)
710             $defs[$key]->setWidth($ncols);
711
712         return HTML::table(array('class' => 'wiki-dl-table',
713                                  'border' => 2, // FIXME: CSS?
714                                  'cellspacing' => 0,
715                                  'cellpadding' => 6),
716                            $defs);
717     }
718 }
719
720 class Block_oldlists extends Block_list
721 {
722     //var $_tag = 'ol', 'ul', or 'dl';
723     var $_re = '(?: [*] (?! \S[^*]* (?<=\S) [*](?!\S) )
724                   | [#]
725                   | ; .* :
726                 ) .*? (?=\S)';
727
728     function _match (&$input, $m) {
729         // FIXME:
730         if (!preg_match('/[*#;]*$/A', $input->getPrefix())) {
731             return false;
732         }
733         
734
735         $prefix = $m->match;
736         $oldindent = '[*#;](?=[#*]|;.*:.*\S)';
737         $newindent = sprintf('\\ {%d}', strlen($prefix));
738         $indent = "(?:$oldindent|$newindent)";
739
740         $bullet = $prefix[0];
741         if ($bullet == '*') {
742             $this->_tag = 'ul';
743             $itemtag = 'li';
744         }
745         elseif ($bullet == '#') {
746             $this->_tag = 'ol';
747             $itemtag = 'li';
748         }
749         else {
750             $this->_tag = 'dl';
751             list ($term,) = explode(':', substr($prefix, 1), 2);
752             $term = trim($term);
753             if ($term)
754                 $this->_content[] = HTML::dt(false, TransformInline($term));
755             $itemtag = 'dd';
756         }
757
758         $this->_content[] = new SubBlock($input, $indent, $m->match, $itemtag);
759         return true;
760     }
761 }
762
763 class Block_pre extends BlockMarkup
764 {
765     var $_re = '<(?:pre|verbatim)>';
766
767     function _match (&$input, $m) {
768         $endtag = '</' . substr($m->match, 1);
769
770         if (($text = $this->_getBlock($input, $endtag)) === false)
771             return false;
772         
773         if (ltrim($m->postmatch))
774             array_unshift($text, $m->postmatch);
775         $text = join("\n", $text);
776         
777         // FIXME: no <img>, <big>, <small>, <sup>, or <sub>'s allowed
778         // in a <pre>.
779         if ($m->match == '<pre>')
780             $text = TransformInline($text);
781
782         $this->_html = HTML::pre(false, $text);
783         return true;
784     }
785
786     function _getBlock (&$input, $end_tag) {
787         $pos = $input->getPos();
788         $text = array();
789
790         while ( ($line = $input->nextLine()) !== false ) {
791             if (rtrim($line) == $end_tag) {
792                 $input->advance();
793                 return $text;
794             }
795             $text[] = $line;
796         }
797         $input->setPos($pos);
798         return false;
799     }
800
801     function finish () {
802         return $this->_html;
803     }
804 }
805
806
807 class Block_plugin extends Block_pre
808 {
809     var $_re = '<\?plugin(?:-form)?(?!\S)';
810
811     // FIXME:
812     /* <?plugin Backlinks
813      *       page=ThisPage ?>
814      *
815      * should work. */
816     function _match (&$input, $m) {
817         $pos = $input->getPos();
818         $pi = $m->match . $m->postmatch;
819         while (!preg_match('/(?<!~)\?>\s*$/', $pi)) {
820             if (($line = $input->nextLine()) === false) {
821                 $input->setPos($pos);
822                 return false;
823             }
824             $pi .= "\n$line";
825         }
826         $input->advance();
827
828         $this->_plugin_pi = $pi;
829         return true;
830     }
831
832     function finish() {
833         global $request;
834         $loader = new WikiPluginLoader;
835
836         return HTML::div(array('class' => 'plugin'),
837                          $loader->expandPI($this->_plugin_pi, $request));
838     }
839 }
840
841 class Block_email_blockquote extends Block_blockquote
842 {
843     // FIXME: move CSS to CSS.
844     var $_attr = array('style' => 'border-left-width: medium; border-left-color: #0f0; border-left-style: ridge; padding-left: 1em; margin-left: 0em; margin-right: 0em;');
845     var $_depth;
846     var $_re = '>\ ?';
847     
848     function _match (&$input, $m) {
849         $indent = str_replace(' ', '\\ ', $m->match);
850         $this->_block = new SubBlock($input, $indent, $m->match,
851                                      'blockquote', $this->_attr);
852         return true;
853     }
854 }
855
856 class Block_hr extends BlockMarkup
857 {
858     var $_re = '-{4,}\s*$';
859
860     function _match (&$input, $m) {
861         $input->advance();
862         return true;
863     }
864
865     function finish () {
866         return HTML::hr();
867     }
868 }
869
870 class Block_heading extends BlockMarkup
871 {
872     var $_re = '!{1,3}';
873     
874     function _match (&$input, $m) {
875         $this->_tag = "h" . (5 - strlen($m->match));
876         $this->_text = TransformInline(trim($m->postmatch));
877         $input->advance();
878         return true;
879     }
880
881     function finish () {
882         return new HtmlElement($this->_tag, false, $this->_text);
883     }
884 }
885
886 class Block_p extends BlockMarkup
887 {
888     var $_tag = 'p';
889     var $_re = '\S.*';
890
891     function _match (&$input, $m) {
892         $this->_text = $m->match;
893         $input->advance();
894         return true;
895     }
896     
897     function merge ($nextBlock, $followsSpace) {
898         $class = get_class($nextBlock);
899         if ($class == 'block_p' && !$followsSpace) {
900             $this->_text .= "\n" . $nextBlock->_text;
901             return $this;
902         }
903         return false;
904     }
905             
906     function finish () {
907         $content = TransformInline(trim($this->_text));
908         return new TightenableParagraph($content);
909     }
910 }
911
912 ////////////////////////////////////////////////////////////////
913 //
914
915
916
917 function TransformText ($text, $markup = 2.0) {
918     if (isa($text, 'WikiDB_PageRevision')) {
919         $rev = $text;
920         $text = $rev->getPackedContent();
921         $markup = $rev->get('markup');
922     }
923
924     if (empty($markup) || $markup < 2.0) {
925         include_once("lib/transform.php");
926         return do_transform($text);
927         //$text = ConvertOldMarkup($text);
928     }
929     
930     // Expand leading tabs.
931     // FIXME: do this better. also move  it...
932     $text = preg_replace('/^\ *[^\ \S\n][^\S\n]*/me', "str_repeat(' ', strlen('\\0'))", $text);
933     assert(!preg_match('/^\ *\t/', $text));
934
935     $output = new WikiText($text);
936     return $output;
937 }
938
939     
940 // (c-file-style: "gnu")
941 // Local Variables:
942 // mode: php
943 // tab-width: 8
944 // c-basic-offset: 4
945 // c-hanging-comment-ender-p: nil
946 // indent-tabs-mode: nil
947 // End:   
948 ?>