1 <?php rcs_id('$Id: BlockParser.php,v 1.63 2007-08-25 18:24:30 rurban Exp $');
2 /* Copyright (C) 2002 Geoffrey T. Dairiki <dairiki@dairiki.org>
3 * Copyright (C) 2004,2005 Reini Urban
5 * This file is part of PhpWiki.
7 * PhpWiki is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * PhpWiki is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with PhpWiki; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 require_once('lib/HtmlElement.php');
22 require_once('lib/CachedMarkup.php');
23 require_once('lib/InlineParser.php');
25 ////////////////////////////////////////////////////////////////
30 * Deal with paragraphs and proper, recursive block indents
31 * for the new style markup (version 2)
33 * Everything which goes over more than line:
34 * automatic lists, UL, OL, DL, table, blockquote, verbatim,
40 * FIXME: unify this with the RegexpSet in InlineParser.
42 * FIXME: This is very php5 sensitive: It was fixed for 1.3.9,
43 * but is again broken with the 1.3.11
44 * allow_call_time_pass_reference clean fixes
47 * @author: Geoffrey T. Dairiki
51 * Return type from RegexpSet::match and RegexpSet::nextMatch.
55 class AnchoredRegexpSet_match {
62 * The text following the matched text.
67 * Index of the regular expression which matched.
73 * A set of regular expressions.
75 * This class is probably only useful for InlineTransformer.
77 class AnchoredRegexpSet
81 * @param $regexps array A list of regular expressions. The
82 * regular expressions should not include any sub-pattern groups
83 * "(...)". (Anonymous groups, like "(?:...)", as well as
84 * look-ahead and look-behind assertions are fine.)
86 function AnchoredRegexpSet ($regexps) {
87 $this->_regexps = $regexps;
88 $this->_re = "/((" . join(")|(", $regexps) . "))/Ax";
92 * Search text for the next matching regexp from the Regexp Set.
94 * @param $text string The text to search.
96 * @return object A RegexpSet_match object, or false if no match.
98 function match ($text) {
99 if (!is_string($text)) return false;
100 if (! preg_match($this->_re, $text, $m)) {
104 $match = new AnchoredRegexpSet_match;
105 $match->postmatch = substr($text, strlen($m[0]));
106 $match->match = $m[1];
107 $match->regexp_ind = count($m) - 3;
112 * Search for next matching regexp.
114 * Here, 'next' has two meanings:
116 * Match the next regexp(s) in the set, at the same position as the last match.
118 * If that fails, match the whole RegexpSet, starting after the position of the
121 * @param $text string Text to search.
123 * @param $prevMatch A RegexpSet_match object
125 * $prevMatch should be a match object obtained by a previous
126 * match upon the same value of $text.
128 * @return object A RegexpSet_match object, or false if no match.
130 function nextMatch ($text, $prevMatch) {
131 // Try to find match at same position.
132 $regexps = array_slice($this->_regexps, $prevMatch->regexp_ind + 1);
137 $pat= "/ ( (" . join(')|(', $regexps) . ") ) /Axs";
139 if (! preg_match($pat, $text, $m)) {
143 $match = new AnchoredRegexpSet_match;
144 $match->postmatch = substr($text, strlen($m[0]));
145 $match->match = $m[1];
146 $match->regexp_ind = count($m) - 3 + $prevMatch->regexp_ind + 1;;
153 class BlockParser_Input {
155 function BlockParser_Input ($text) {
157 // Expand leading tabs.
158 // FIXME: do this better.
160 // We want to ensure the only characters matching \s are ' ' and "\n".
162 $text = preg_replace('/(?![ \n])\s/', ' ', $text);
163 assert(!preg_match('/(?![ \n])\s/', $text));
165 $this->_lines = preg_split('/[^\S\n]*\n/', $text);
168 // Strip leading blank lines.
169 while ($this->_lines and ! $this->_lines[0])
170 array_shift($this->_lines);
171 $this->_atSpace = false;
174 function skipSpace () {
175 $nlines = count($this->_lines);
177 if ($this->_pos >= $nlines) {
178 $this->_atSpace = false;
181 if ($this->_lines[$this->_pos] != '')
184 $this->_atSpace = true;
186 return $this->_atSpace;
189 function currentLine () {
190 if ($this->_pos >= count($this->_lines)) {
193 return $this->_lines[$this->_pos];
196 function nextLine () {
197 $this->_atSpace = $this->_lines[$this->_pos++] === '';
198 if ($this->_pos >= count($this->_lines)) {
201 return $this->_lines[$this->_pos];
204 function advance () {
205 $this->_atSpace = ($this->_lines[$this->_pos] === '');
210 return array($this->_pos, $this->_atSpace);
213 function setPos ($pos) {
214 list($this->_pos, $this->_atSpace) = $pos;
217 function getPrefix () {
221 function getDepth () {
226 if ($this->_pos < count($this->_lines))
227 return $this->_lines[$this->_pos];
232 function _debug ($tab, $msg) {
234 $where = $this->where();
235 $tab = str_repeat('____', $this->getDepth() ) . $tab;
236 printXML(HTML::div("$tab $msg: at: '",
243 class BlockParser_InputSubBlock extends BlockParser_Input
245 function BlockParser_InputSubBlock (&$input, $prefix_re, $initial_prefix = false) {
246 $this->_input = &$input;
247 $this->_prefix_pat = "/$prefix_re|\\s*\$/Ax";
248 $this->_atSpace = false;
250 if (($line = $input->currentLine()) === false)
251 $this->_line = false;
252 elseif ($initial_prefix) {
253 assert(substr($line, 0, strlen($initial_prefix)) == $initial_prefix);
254 $this->_line = (string) substr($line, strlen($initial_prefix));
255 $this->_atBlank = ! ltrim($line);
257 elseif (preg_match($this->_prefix_pat, $line, $m)) {
258 $this->_line = (string) substr($line, strlen($m[0]));
259 $this->_atBlank = ! ltrim($line);
262 $this->_line = false;
265 function skipSpace () {
266 // In contrast to the case for top-level blocks,
267 // for sub-blocks, there never appears to be any trailing space.
268 // (The last block in the sub-block should always be of class tight-bottom.)
269 while ($this->_line === '')
272 if ($this->_line === false)
273 return $this->_atSpace == 'strong_space';
275 return $this->_atSpace;
278 function currentLine () {
282 function nextLine () {
283 if ($this->_line === '')
284 $this->_atSpace = $this->_atBlank ? 'weak_space' : 'strong_space';
286 $this->_atSpace = false;
288 $line = $this->_input->nextLine();
289 if ($line !== false && preg_match($this->_prefix_pat, $line, $m)) {
290 $this->_line = (string) substr($line, strlen($m[0]));
291 $this->_atBlank = ! ltrim($line);
294 $this->_line = false;
299 function advance () {
304 return array($this->_line, $this->_atSpace, $this->_input->getPos());
307 function setPos ($pos) {
308 $this->_line = $pos[0];
309 $this->_atSpace = $pos[1];
310 $this->_input->setPos($pos[2]);
313 function getPrefix () {
314 assert ($this->_line !== false);
315 $line = $this->_input->currentLine();
316 assert ($line !== false && strlen($line) >= strlen($this->_line));
317 return substr($line, 0, strlen($line) - strlen($this->_line));
320 function getDepth () {
321 return $this->_input->getDepth() + 1;
325 return $this->_input->where();
330 class Block_HtmlElement extends HtmlElement
332 function Block_HtmlElement($tag /*, ... */) {
333 $this->_init(func_get_args());
336 function setTightness($top, $bottom) {
337 $this->setInClass('tightenable');
338 $this->setInClass('top', $top);
339 $this->setInClass('bottom', $bottom);
343 class ParsedBlock extends Block_HtmlElement {
345 function ParsedBlock (&$input, $tag = 'div', $attr = false) {
346 $this->Block_HtmlElement($tag, $attr);
347 $this->_initBlockTypes();
348 $this->_parse($input);
351 function _parse (&$input) {
352 // php5 failed to advance the block. php5 copies objects by ref.
353 // nextBlock == block, both are the same objects. So we have to clone it.
354 for ($block = $this->_getBlock($input);
356 $block = (is_object($nextBlock) ? clone($nextBlock) : $nextBlock))
358 while ($nextBlock = $this->_getBlock($input)) {
359 // Attempt to merge current with following block.
360 if (! ($merged = $block->merge($nextBlock)) ) {
361 break; // can't merge
365 $this->pushContent($block->finish());
369 // FIXME: hackish. This should only be called once.
370 function _initBlockTypes () {
371 // better static or global?
372 static $_regexpset, $_block_types;
374 if (!is_object($_regexpset)) {
376 ('oldlists', 'list', 'dl', 'table_dl',
377 'blockquote', 'heading', 'hr', 'pre', 'email_blockquote',
379 // insert it before p!
380 if (ENABLE_MARKUP_DIVSPAN) {
381 array_pop($Block_types);
382 $Block_types[] = 'divspan';
383 $Block_types[] = 'p';
385 foreach ($Block_types as $type) {
386 $class = "Block_$type";
388 $this->_block_types[] = $proto;
389 $this->_regexps[] = $proto->_re;
391 $this->_regexpset = new AnchoredRegexpSet($this->_regexps);
392 $_regexpset = $this->_regexpset;
393 $_block_types = $this->_block_types;
396 $this->_regexpset = $_regexpset;
397 $this->_block_types = $_block_types;
401 function _getBlock (&$input) {
402 $this->_atSpace = $input->skipSpace();
404 $line = $input->currentLine();
405 if ($line === false or $line === '') { // allow $line === '0'
408 $tight_top = !$this->_atSpace;
409 $re_set = &$this->_regexpset;
410 //FIXME: php5 fails to advance here!
411 for ($m = $re_set->match($line); $m; $m = $re_set->nextMatch($line, $m)) {
412 $block = clone($this->_block_types[$m->regexp_ind]);
413 if (DEBUG & _DEBUG_PARSER)
414 $input->_debug('>', get_class($block));
416 if ($block->_match($input, $m)) {
417 //$block->_text = $line;
418 if (DEBUG & _DEBUG_PARSER)
419 $input->_debug('<', get_class($block));
420 $tight_bottom = ! $input->skipSpace();
421 $block->_setTightness($tight_top, $tight_bottom);
424 if (DEBUG & _DEBUG_PARSER)
425 $input->_debug('[', "_match failed");
427 if ($line === false or $line === '') // allow $line === '0'
430 trigger_error("Couldn't match block: '$line'", E_USER_NOTICE);
435 class WikiText extends ParsedBlock {
436 function WikiText ($text) {
437 $input = new BlockParser_Input($text);
438 $this->ParsedBlock($input);
442 class SubBlock extends ParsedBlock {
443 function SubBlock (&$input, $indent_re, $initial_indent = false,
444 $tag = 'div', $attr = false) {
445 $subinput = new BlockParser_InputSubBlock($input, $indent_re, $initial_indent);
446 $this->ParsedBlock($subinput, $tag, $attr);
451 * TightSubBlock is for use in parsing lists item bodies.
453 * If the sub-block consists of a single paragraph, it omits
454 * the paragraph element.
456 * We go to this trouble so that "tight" lists look somewhat reasonable
457 * in older (non-CSS) browsers. (If you don't do this, then, without
458 * CSS, you only get "loose" lists.
460 class TightSubBlock extends SubBlock {
461 function TightSubBlock (&$input, $indent_re, $initial_indent = false,
462 $tag = 'div', $attr = false) {
463 $this->SubBlock($input, $indent_re, $initial_indent, $tag, $attr);
465 // If content is a single paragraph, eliminate the paragraph...
466 if (count($this->_content) == 1) {
467 $elem = $this->_content[0];
468 if (isa($elem, 'XmlElement') and $elem->getTag() == 'p') {
469 assert($elem->getAttr('class') == 'tightenable top bottom');
470 $this->setContent($elem->getContent());
479 function _match (&$input, $match) {
480 trigger_error('pure virtual', E_USER_ERROR);
483 function _setTightness ($top, $bot) {
484 $this->_element->setTightness($top, $bot);
487 function merge ($followingBlock) {
492 return $this->_element;
496 class Block_blockquote extends BlockMarkup
499 var $_re = '\ +(?=\S)';
501 function _match (&$input, $m) {
502 $this->_depth = strlen($m->match);
503 $indent = sprintf("\\ {%d}", $this->_depth);
504 $this->_element = new SubBlock($input, $indent, $m->match,
509 function merge ($nextBlock) {
510 if (get_class($nextBlock) == get_class($this)) {
511 assert ($nextBlock->_depth < $this->_depth);
512 $nextBlock->_element->unshiftContent($this->_element);
513 if (!empty($this->_tight_top))
514 $nextBlock->_tight_top = $this->_tight_top;
521 class Block_list extends BlockMarkup
523 //var $_tag = 'ol' or 'ul';
529 | [*] (?!(?=\S)[^*]*(?<=\S)[*](?:\\s|[-)}>"\'\\/:.,;!?_*=]) )
531 var $_content = array();
533 function _match (&$input, $m) {
534 // A list as the first content in a list is not allowed.
537 // Should markup as <ul><li>* Item</li></ul>,
538 // not <ul><li><ul><li>Item</li></ul>/li></ul>.
540 if (preg_match('/[*#+-o]/', $input->getPrefix())) {
545 $indent = sprintf("\\ {%d}", strlen($prefix));
547 $bullet = trim($m->match);
548 $this->_tag = $bullet == '#' ? 'ol' : 'ul';
549 $this->_content[] = new TightSubBlock($input, $indent, $m->match, 'li');
553 function _setTightness($top, $bot) {
554 $li = &$this->_content[0];
555 $li->setTightness($top, $bot);
558 function merge ($nextBlock) {
559 if (isa($nextBlock, 'Block_list') and $this->_tag == $nextBlock->_tag) {
560 if ($nextBlock->_content === $this->_content) {
561 trigger_error("Internal Error: no block advance", E_USER_NOTICE);
564 array_splice($this->_content, count($this->_content), 0,
565 $nextBlock->_content);
572 return new Block_HtmlElement($this->_tag, false, $this->_content);
576 class Block_dl extends Block_list
580 function Block_dl () {
581 $this->_re = '\ {0,4}\S.*(?<!'.ESCAPE_CHAR.'):\s*$';
584 function _match (&$input, $m) {
585 if (!($p = $this->_do_match($input, $m)))
587 list ($term, $defn, $loose) = $p;
589 $this->_content[] = new Block_HtmlElement('dt', false, $term);
590 $this->_content[] = $defn;
591 $this->_tight_defn = !$loose;
595 function _setTightness($top, $bot) {
596 $dt = &$this->_content[0];
597 $dd = &$this->_content[1];
599 $dt->setTightness($top, $this->_tight_defn);
600 $dd->setTightness($this->_tight_defn, $bot);
603 function _do_match (&$input, $m) {
604 $pos = $input->getPos();
606 $firstIndent = strspn($m->match, ' ');
607 $pat = sprintf('/\ {%d,%d}(?=\s*\S)/A', $firstIndent + 1, $firstIndent + 5);
610 $loose = $input->skipSpace();
611 $line = $input->currentLine();
613 if (!$line || !preg_match($pat, $line, $mm)) {
614 $input->setPos($pos);
615 return false; // No body found.
618 $indent = strlen($mm[0]);
619 $term = TransformInline(rtrim(substr(trim($m->match),0,-1)));
620 $defn = new TightSubBlock($input, sprintf("\\ {%d}", $indent), false, 'dd');
621 return array($term, $defn, $loose);
627 class Block_table_dl_defn extends XmlContent
632 function Block_table_dl_defn ($term, $defn) {
634 if (!is_array($defn))
635 $defn = $defn->getContent();
637 $this->_next_tight_top = false; // value irrelevant - gets fixed later
638 $this->_ncols = $this->_ComputeNcols($defn);
641 foreach ($defn as $item) {
642 if ($this->_IsASubtable($item))
643 $this->_addSubtable($item);
645 $this->_addToRow($item);
649 $th = HTML::th($term);
650 if ($this->_nrows > 1)
651 $th->setAttr('rowspan', $this->_nrows);
652 $this->_setTerm($th);
655 function setTightness($tight_top, $tight_bot) {
656 $this->_tight_top = $tight_top;
657 $this->_tight_bot = $tight_bot;
658 $first = &$this->firstTR();
659 $last = &$this->lastTR();
660 $first->setInClass('top', $tight_top);
662 $last->setInClass('bottom', $tight_bot);
664 trigger_error(sprintf("no lastTR: %s",AsXML($this->_content[0])), E_USER_WARNING);
668 function _addToRow ($item) {
669 if (empty($this->_accum)) {
670 $this->_accum = HTML::td();
671 if ($this->_ncols > 2)
672 $this->_accum->setAttr('colspan', $this->_ncols - 1);
674 $this->_accum->pushContent($item);
677 function _flushRow ($tight_bottom=false) {
678 if (!empty($this->_accum)) {
679 $row = new Block_HtmlElement('tr', false, $this->_accum);
681 $row->setTightness($this->_next_tight_top, $tight_bottom);
682 $this->_next_tight_top = $tight_bottom;
684 $this->pushContent($row);
685 $this->_accum = false;
690 function _addSubtable ($table) {
691 if (!($table_rows = $table->getContent()))
694 $this->_flushRow($table_rows[0]->_tight_top);
696 foreach ($table_rows as $subdef) {
697 $this->pushContent($subdef);
698 $this->_nrows += $subdef->nrows();
699 $this->_next_tight_top = $subdef->_tight_bot;
703 function _setTerm ($th) {
704 $first_row = &$this->_content[0];
705 if (isa($first_row, 'Block_table_dl_defn'))
706 $first_row->_setTerm($th);
708 $first_row->unshiftContent($th);
711 function _ComputeNcols ($defn) {
713 foreach ($defn as $item) {
714 if ($this->_IsASubtable($item)) {
715 $row = $this->_FirstDefn($item);
716 $ncols = max($ncols, $row->ncols() + 1);
722 function _IsASubtable ($item) {
723 return isa($item, 'HtmlElement')
724 && $item->getTag() == 'table'
725 && $item->getAttr('class') == 'wiki-dl-table';
728 function _FirstDefn ($subtable) {
729 $defs = $subtable->getContent();
734 return $this->_ncols;
738 return $this->_nrows;
741 function & firstTR() {
742 $first = &$this->_content[0];
743 if (isa($first, 'Block_table_dl_defn'))
744 return $first->firstTR();
748 function & lastTR() {
749 $last = &$this->_content[$this->_nrows - 1];
750 if (isa($last, 'Block_table_dl_defn'))
751 return $last->lastTR();
755 function setWidth ($ncols) {
756 assert($ncols >= $this->_ncols);
757 if ($ncols <= $this->_ncols)
759 $rows = &$this->_content;
760 for ($i = 0; $i < count($rows); $i++) {
762 if (isa($row, 'Block_table_dl_defn'))
763 $row->setWidth($ncols - 1);
765 $n = count($row->_content);
766 $lastcol = &$row->_content[$n - 1];
767 if (!empty($lastcol))
768 $lastcol->setAttr('colspan', $ncols - 1);
774 class Block_table_dl extends Block_dl
776 var $_tag = 'dl-table'; // phony.
778 function Block_table_dl() {
779 $this->_re = '\ {0,4} (?:\S.*)? (?<!'.ESCAPE_CHAR.') \| \s* $';
782 function _match (&$input, $m) {
783 if (!($p = $this->_do_match($input, $m)))
785 list ($term, $defn, $loose) = $p;
787 $this->_content[] = new Block_table_dl_defn($term, $defn);
791 function _setTightness($top, $bot) {
792 $this->_content[0]->setTightness($top, $bot);
797 $defs = &$this->_content;
800 foreach ($defs as $defn)
801 $ncols = max($ncols, $defn->ncols());
803 foreach ($defs as $key => $defn)
804 $defs[$key]->setWidth($ncols);
806 return HTML::table(array('class' => 'wiki-dl-table',
814 class Block_oldlists extends Block_list
816 //var $_tag = 'ol', 'ul', or 'dl';
817 var $_re = '(?: [*] (?!(?=\S)[^*]*(?<=\S)[*](?:\\s|[-)}>"\'\\/:.,;!?_*=]))
818 | [#] (?! \[ .*? \] )
822 function _match (&$input, $m) {
824 if (!preg_match('/[*#;]*$/A', $input->getPrefix())) {
830 $oldindent = '[*#;](?=[#*]|;.*:.*\S)';
831 $newindent = sprintf('\\ {%d}', strlen($prefix));
832 $indent = "(?:$oldindent|$newindent)";
834 $bullet = $prefix[0];
835 if ($bullet == '*') {
839 elseif ($bullet == '#') {
845 list ($term,) = explode(':', substr($prefix, 1), 2);
848 $this->_content[] = new Block_HtmlElement('dt', false,
849 TransformInline($term));
853 $this->_content[] = new TightSubBlock($input, $indent, $m->match, $itemtag);
857 function _setTightness($top, $bot) {
858 if (count($this->_content) == 1) {
859 $li = &$this->_content[0];
860 $li->setTightness($top, $bot);
863 // This is where php5 usually brakes.
864 // wrong duplicated <li> contents
865 if (DEBUG and DEBUG & _DEBUG_PARSER and check_php_version(5)) {
866 if (count($this->_content) != 2) {
869 $class = new Reflection_Class('XmlElement');
870 // Print out basic information
872 "===> The %s%s%s %s '%s' [extends %s]\n".
875 " having the modifiers %d [%s]\n",
876 $class->isInternal() ? 'internal' : 'user-defined',
877 $class->isAbstract() ? ' abstract' : '',
878 $class->isFinal() ? ' final' : '',
879 $class->isInterface() ? 'interface' : 'class',
881 var_export($class->getParentClass(), 1),
882 $class->getFileName(),
883 $class->getStartLine(),
884 $class->getEndline(),
885 $class->getModifiers(),
886 implode(' ', Reflection::getModifierNames($class->getModifiers()))
888 // Print class properties
889 printf("---> Properties: %s\n", var_export($class->getProperties(), 1));
891 echo 'count($this->_content): ', count($this->_content),"\n";
892 echo "\$this->_content[0]: "; var_dump ($this->_content[0]);
894 for ($i=1; $i < min(5, count($this->_content)); $i++) {
895 $c =& $this->_content[$i];
896 echo '$this->_content[',$i,"]: \n";
897 echo "_tag: "; var_dump ($c->_tag);
898 echo "_content: "; var_dump ($c->_content);
899 echo "_properties: "; var_dump ($c->_properties);
901 debug_print_backtrace();
902 if (DEBUG & _DEBUG_APD) {
903 if (function_exists("xdebug_get_function_stack")) {
904 var_dump (xdebug_get_function_stack());
910 if (!check_php_version(5))
911 assert(count($this->_content) == 2);
912 $dt = &$this->_content[0];
913 $dd = &$this->_content[1];
914 $dt->setTightness($top, false);
915 $dd->setTightness(false, $bot);
920 class Block_pre extends BlockMarkup
922 var $_re = '<(?:pre|verbatim|nowiki)>';
924 function _match (&$input, $m) {
925 $endtag = '</' . substr($m->match, 1);
927 $pos = $input->getPos();
929 $line = $m->postmatch;
930 while (ltrim($line) != $endtag) {
932 if (($line = $input->nextLine()) === false) {
933 $input->setPos($pos);
939 if ($m->match == '<nowiki>')
940 $text = join("<br>\n", $text);
942 $text = join("\n", $text);
944 // FIXME: no <img>, <big>, <small>, <sup>, or <sub>'s allowed
946 if ($m->match == '<pre>')
947 $text = TransformInline($text);
948 if ($m->match == '<nowiki>') {
949 $text = TransformInlineNowiki($text);
950 $this->_element = new Block_HtmlElement('p', false, $text);
952 $this->_element = new Block_HtmlElement('pre', false, $text);
958 class Block_plugin extends Block_pre
960 var $_re = '<\?plugin(?:-form)?(?!\S)';
963 /* <?plugin Backlinks
965 /* <?plugin ListPages pages=<!plugin-list Backlinks!>
966 * exclude=<!plugin-list TitleSearch s=T*!> ?>
970 function _match (&$input, $m) {
971 $pos = $input->getPos();
972 $pi = $m->match . $m->postmatch;
973 while (!preg_match('/(?<!'.ESCAPE_CHAR.')\?>\s*$/', $pi)) {
974 if (($line = $input->nextLine()) === false) {
975 $input->setPos($pos);
982 $this->_element = new Cached_PluginInvocation($pi);
987 class Block_email_blockquote extends BlockMarkup
989 var $_attr = array('class' => 'mail-style-quote');
992 function _match (&$input, $m) {
993 //$indent = str_replace(' ', '\\ ', $m->match) . '|>$';
994 $indent = $this->_re;
995 $this->_element = new SubBlock($input, $indent, $m->match,
996 'blockquote', $this->_attr);
1001 class Block_hr extends BlockMarkup
1003 var $_re = '-{4,}\s*$';
1005 function _match (&$input, $m) {
1007 $this->_element = new Block_HtmlElement('hr');
1011 function _setTightness($top, $bot) {
1012 // Don't tighten <hr/>s
1016 class Block_heading extends BlockMarkup
1018 var $_re = '!{1,3}';
1020 function _match (&$input, $m) {
1021 $tag = "h" . (5 - strlen($m->match));
1022 $text = TransformInline(trim($m->postmatch));
1025 $this->_element = new Block_HtmlElement($tag, false, $text);
1030 function _setTightness($top, $bot) {
1031 // Don't tighten headers.
1035 class Block_p extends BlockMarkup
1041 function _match (&$input, $m) {
1042 $this->_text = $m->match;
1047 function _setTightness ($top, $bot) {
1048 $this->_tight_top = $top;
1049 $this->_tight_bot = $bot;
1052 function merge ($nextBlock) {
1053 $class = get_class($nextBlock);
1054 if (strtolower($class) == 'block_p' and $this->_tight_bot) {
1055 $this->_text .= "\n" . $nextBlock->_text;
1056 $this->_tight_bot = $nextBlock->_tight_bot;
1062 function finish () {
1063 $content = TransformInline(trim($this->_text));
1064 $p = new Block_HtmlElement('p', false, $content);
1065 $p->setTightness($this->_tight_top, $this->_tight_bot);
1070 class Block_divspan extends BlockMarkup
1072 var $_re = '<(?im)(?: div|span)(?:[^>]*)?>';
1074 function _match (&$input, $m) {
1075 if (substr($m->match,1,4) == 'span') {
1081 $argstr = substr(trim(substr($m->match,strlen($tag)+1)),0,-1);
1082 $pos = $input->getPos();
1083 $pi = $content = $m->postmatch;
1084 while (!preg_match('/^(.*)\<\/'.$tag.'\>(.*)$/i', $pi, $me)) {
1085 if ($pi != $content)
1086 $content .= "\n$pi";
1087 if (($pi = $input->nextLine()) === false) {
1088 $input->setPos($pos);
1092 if ($pi != $content)
1093 $content .= $me[1]; // prematch
1097 if (strstr($content, "\n"))
1098 $content = TransformText($content);
1100 $content = TransformInline($content);
1105 while (preg_match("/(\w+)=(.+)/", $argstr, $m)) {
1106 $k = $m[1]; $v = $m[2];
1107 if (preg_match("/^\"(.+?)\"(.*)$/", $v, $m)) {
1111 preg_match("/^(\s+)(.*)$/", $v, $m);
1115 if (trim($k) and trim($v)) $args[$k] = $v;
1118 $this->_element = new Block_HtmlElement($tag, $args, $content);
1119 //$this->_element->setTightness($tag == 'span', $tag == 'span');
1122 function _setTightness($top, $bot) {
1123 // Don't tighten user <div|span>
1128 ////////////////////////////////////////////////////////////////
1132 * Transform the text of a page, and return a parse tree.
1134 function TransformTextPre ($text, $markup = 2.0, $basepage=false) {
1135 if (isa($text, 'WikiDB_PageRevision')) {
1137 $text = $rev->getPackedContent();
1138 $markup = $rev->get('markup');
1140 // NEW: default markup is new, to increase stability
1141 if (!empty($markup) && $markup < 2.0) {
1142 $text = ConvertOldMarkup($text);
1145 /*if (!empty($markup) && $markup == 3) {
1146 $text = ConvertFromCreole($text);
1148 // Expand leading tabs.
1149 $text = expand_tabs($text);
1150 //set_time_limit(3);
1151 $output = new WikiText($text);
1157 * Transform the text of a page, and return an XmlContent,
1158 * suitable for printXml()-ing.
1160 function TransformText ($text, $markup = 2.0, $basepage = false) {
1161 $output = TransformTextPre($text, $markup, $basepage);
1163 // This is for immediate consumption.
1164 // We must bind the contents to a base pagename so that
1165 // relative page links can be properly linkified...
1166 return new CacheableMarkup($output->getContent(), $basepage);
1168 return new XmlContent($output->getContent());
1171 // $Log: not supported by cvs2svn $
1172 // Revision 1.62 2007/08/10 21:54:08 rurban
1173 // fix DIVSPAN parsing
1175 // Revision 1.61 2007/07/14 17:55:29 rurban
1178 // Revision 1.60 2007/01/07 18:41:32 rurban
1179 // Fix ENABLE_DIVSPAN
1181 // Revision 1.59 2006/12/22 16:21:29 rurban
1182 // silence one BlockParser use-case
1184 // Revision 1.58 2006/11/19 13:57:14 rurban
1185 // fix Regex Syntax Error
1187 // Revision 1.57 2006/10/12 06:32:30 rurban
1188 // Optionally support new tags <div>, <span> with ENABLE_MARKUP_DIVSPAN (in work)
1190 // Revision 1.56 2006/07/23 14:03:18 rurban
1191 // add new feature: DISABLE_MARKUP_WIKIWORD
1193 // Revision 1.55 2005/01/29 21:08:41 rurban
1196 // Revision 1.54 2005/01/29 21:00:54 rurban
1197 // do not warn on empty nextBlock
1199 // Revision 1.53 2005/01/29 20:36:44 rurban
1200 // very important php5 fix! clone objects
1202 // Revision 1.52 2004/10/21 19:52:10 rurban
1203 // Patch #994487: Allow callers to get the parse tree for a page (danfr)
1205 // Revision 1.51 2004/09/14 09:54:04 rurban
1206 // cache ParsedBlock::_initBlockTypes
1208 // Revision 1.50 2004/09/08 13:38:00 rurban
1209 // improve loadfile stability by using markup=2 as default for undefined markup-style.
1210 // use more refs for huge objects.
1211 // fix debug=static issue in WikiPluginCached
1213 // Revision 1.49 2004/07/02 09:55:58 rurban
1214 // more stability fixes: new DISABLE_GETIMAGESIZE if your php crashes when loading LinkIcons: failing getimagesize in old phps; blockparser stabilized
1216 // Revision 1.48 2004/06/21 06:30:16 rurban
1217 // revert to prev references
1219 // Revision 1.47 2004/06/20 15:30:04 rurban
1220 // get_class case-sensitivity issues
1222 // Revision 1.46 2004/06/20 14:42:53 rurban
1223 // various php5 fixes (still broken at blockparser)
1226 // (c-file-style: "gnu")
1230 // c-basic-offset: 4
1231 // c-hanging-comment-ender-p: nil
1232 // indent-tabs-mode: nil