WikiTransform();
// register functions
// functions are applied in order of registering
$this->register(WT_SIMPLE_MARKUP, 'wtm_plugin_link');
$this->register(WT_TOKENIZER, 'wtt_doublebrackets', '\[\[');
//$this->register(WT_TOKENIZER, 'wtt_footnotes', '^\[\d+\]');
//$this->register(WT_TOKENIZER, 'wtt_footnoterefs', '\[\d+\]');
$this->register(WT_TOKENIZER, 'wtt_bracketlinks', '\[.+?\]');
$this->register(WT_TOKENIZER, 'wtt_urls',
"!?\b($AllowedProtocols):[^\s<>\[\]\"'()]*[^\s<>\[\]\"'(),.?]");
if (function_exists('wtt_interwikilinks')) {
$this->register(WT_TOKENIZER, 'wtt_interwikilinks',
pcre_fix_posix_classes("!?(?register(WT_TOKENIZER, 'wtt_bumpylinks', "!?$WikiNameRegexp");
$this->register(WT_SIMPLE_MARKUP, 'wtm_htmlchars');
$this->register(WT_SIMPLE_MARKUP, 'wtm_linebreak');
$this->register(WT_SIMPLE_MARKUP, 'wtm_bold_italics');
}
};
function TransformInline ($text) {
// the old transform code does funny things with leading and trailing
// white space.
// All this is to ensure leading and trailing whitespace does not
// get altered...
$lines = explode("\n", $text);
$out = '';
while ($lines && !preg_match('/\S/', $lines[0]))
$out .= array_shift($lines) . "\n";
$tail = '';
while ($lines && !preg_match('/\S/', $lines[count($lines)-1]))
$tail = array_pop($lines) . "\n$tail";
$trfm = new InlineTransform;
$out .= preg_replace('/\n $/', '', AsXML($trfm->do_transform('', $lines)));
$out .= $tail;
//if ($out != $text) {
// echo(" IN
'" . htmlspecialchars($text) . "'
\n");
// echo("OUT '" . htmlspecialchars($out) . "'
\n");
//}
return new RawXml($out);
}
define("BLOCK_NEVER_TIGHTEN", 0);
define("BLOCK_NOTIGHTEN_AFTER", 1);
define("BLOCK_NOTIGHTEN_BEFORE", 2);
define("BLOCK_NOTIGHTEN_EITHER", 3);
/**
* FIXME:
* Still to do:
* (old-style) tables
* style tables
* old-style lists.
*/
class BlockParser {
function BlockParser ($block_types) {
$this->_blockTypes = array();
foreach ($block_types as $type)
$this->registerBlockType($type);
}
function registerBlockType ($class) {
// FIXME: this is hackish.
// It's because there seems to be no way to
// call static members of $class.
$prototype = new $class (false);
$this->_blockTypes[] = $prototype;
}
function parse ($text, $tighten = BLOCK_NEVER_TIGHTEN) {
$content = array();
$block = $this->_nextBlock($text);
$tight = $tighten != BLOCK_NEVER_TIGHTEN;
while ($block !== false) {
if (!$block) {
if (($tighten & BLOCK_NOTIGHTEN_AFTER) != 0)
$tight = false;
$block = $this->_nextBlock($text);
continue;
}
while ($nextBlock = $this->_nextBlock($text)) {
// Attempt to merge current with following block.
if (! $nextBlock->mergeWithPrecedingBlock($block))
break; // can't merge
$block = $nextBlock;
}
if ($nextBlock === '' && ($tighten & BLOCK_NOTIGHTEN_BEFORE) != 0)
$tight = false;
$out = $block->finish($tight);
if (is_array($out))
array_splice($content, count($content), 0, $out);
else
$content[] = $out;
$block = $nextBlock;
$tight = $tighten != BLOCK_NEVER_TIGHTEN;
}
return $content;
}
function _nextBlock (&$text) {
if (!$text)
return false;
if (preg_match('/\s*\n/A', $text, $m)) {
// A paragraph break: one or more blank lines.
$text = substr($text, strlen($m[0]));
return ''; // (empty but !== false).
}
foreach ($this->_blockTypes as $type) {
if (($block = $type->Match($text))) {
return $block;
}
}
// We should never get here.
list ($line1, $line2) = explode("\n", $text);
trigger_error("Couldn't match block:\n $line1\n $line2",
E_USER_ERROR);
}
}
class Block extends HtmlElement {
/**
* (This should be a static member function...)
*/
function Match (&$text) {
if (!preg_match($this->_re, $text, $m))
return false;
$block = $this;
$block->_parse($m, $text);
return $block;
}
function _parse ($m, &$text) {
$this->_init_from_match($m);
$text = substr($text, strlen($m[0]));
}
function mergeWithPrecedingBlock ($precedingBlock) {
return false;
}
function finish ($tighten) {
return $this;
}
}
class Block_list extends Block
{
var $_re = '/\ {0,4}([*#])\ *(?=\S)/A';
function _parse ($m, &$text) {
global $BlockParser;
$li_pfx = preg_quote($m[0], '/');
$c_pfx = sprintf("\\ {%d}", strlen($m[0]));
$li_re = "/${li_pfx}\S.*(?:\s*\n${c_pfx}.*\S.*)*\n?/A";
$strip_re = "/^(?:${li_pfx}|${c_pfx})/m";
$this->_init($m[1] == '*' ? 'ul' : 'ol');
$tight = BLOCK_NOTIGHTEN_AFTER;
$length = 0;
while (preg_match($li_re, $text, $m)) {
$text = substr($text, strlen($m[0]));
$body = preg_replace($strip_re, '', $m[0]);
$this->pushContent(HTML::li(false,
$BlockParser->parse(rtrim($body), $tight)));
if ($pbreak = preg_match("/\s*\n/A", $text, $m2)) {
$text = substr($text, strlen($m2[0]));
$tight = BLOCK_NEVER_TIGHTEN;
}
else
$tight = BLOCK_NOTIGHTEN_AFTER;
}
assert(!$this->isEmpty());
}
}
class Block_dl extends Block
{
var $_re = '/ [^\s!].*: # term (- )
\s*\n # zero of more blank lines
\ +\S # indented non-blank line
/Ax';
var $_dtdd_re = '/ ([^\s!].*): # term (
- )
[^\S\n]*\n # rest of first line.
((?:
(?:\s*\n)? # zero of more blank lines
\ +\S.* # indented non-blank line
)+)\n?
/Ax';
function Block_dl () {
$this->_init('dl');
}
function _parse ($m, &$text) {
global $BlockParser;
while(preg_match($this->_dtdd_re, $text, $m)) {
$text = substr($text, strlen($m[0]));
$dt = HTML::dt(rtrim($m[1]));
$body = $this->_unindent($m[2]);
$dd = HTML::dd(false, $BlockParser->parse($body, BLOCK_NOTIGHTEN_AFTER));
if (preg_match("/\s*\n/A", $text, $m2)) {
$text = substr($text, strlen($m2[0]));
// FIXME: use something else for space here?
$dd->pushContent(HTML::p(array('class' => 'empty')));
}
$this->pushContent($dt, $dd);
}
assert(!$this->isEmpty());
}
function _unindent ($body) {
assert(preg_match_all("/^ +(?=\S)/m", $body, $m));
$indent = strlen($m[0][0]);
foreach ($m[0] as $pfx)
$indent = min($indent, strlen($pfx));
$ind_re = sprintf("\\ {%d}", $indent);
return preg_replace("/^{$ind_re}/m", "", $body);
}
}
class Block_blockquote extends Block
{
//var $_depth;
var $_re = '/ (\ +(?=\S)).* # indented non-blank line
(?:
\s*\n # zero or more blank lines
\1.* # lines with same or greater indent
)*\n?
/Ax';
function Block_blockquote () {
$this->_init('blockquote');
}
function _init_from_match ($m) {
$this->_depth = strlen($m[1]);
$pfx = preg_quote($m[1], '/');
$body = preg_replace("/^$pfx/m", "", $m[0]);
global $BlockParser;
$this->pushContent($BlockParser->parse($body, BLOCK_NOTIGHTEN_EITHER));
}
function mergeWithPrecedingBlock ($precedingBlock) {
if (!isa($precedingBlock, "Block_blockquote"))
return false; // can only merge with another blockquote.
if ($precedingBlock->_depth <= $this->_depth)
return false; // can only merge with deeper block
$this->unshiftContent($precedingBlock);
return true;
}
}
class BlockBlock extends Block
{
function BlockBlock ($begin_re, $end_re) {
$this->_re = "/ (
$begin_re
( (?:.|\\n)*? )
$end_re
)
[^\\S\\n]*\\n?
/Aix";
}
}
class Block_pre extends BlockBlock
{
function Block_pre () {
$this->_init('pre');
$this->BlockBlock("
", "(?");
}
function _init_from_match ($m) {
$this->pushContent(TransformInline($m[2]));
}
}
class Block_plugin extends BlockBlock
{
function Block_plugin () {
$this->_init('div', array('class' => 'plugin'));
$this->BlockBlock("<\?plugin(?:-form)?\s", "\?>");
}
function _init_from_match ($m) {
global $request;
$loader = new WikiPluginLoader;
$this->pushContent($loader->expandPI($m[1], $request));
}
}
class Block_hr extends Block
{
var $_re = "/-{4,}[^\S\n]*\n?/A";
function Block_hr () {
$this->_init('hr');
}
function _init_from_match ($m) {
//return true;
}
}
class Block_heading extends Block
{
var $_re = "/(!{1,3}).*?(\S.*)\n?/A";
function _init_from_match ($m) {
$tag = "h" . (5 - strlen($m[1]));
$this->_init($tag);
$this->pushContent(TransformInline(rtrim($m[2])));
}
}
class Block_p extends Block
{
var $_re = "/\S.*\n?/A";
//var $_text = '';
function Block_p () {
$this->_init('p');
}
function _init_from_match ($m) {
$this->_text = $m[0];
}
function mergeWithPrecedingBlock ($precedingBlock) {
if (!isa($precedingBlock, 'Block_p'))
return false;
$this->_text = $precedingBlock->_text . $this->_text;
return true;
}
function finish ($tighten) {
$content = TransformInline(trim($this->_text));
if ($tighten)
return $content;
else {
$this->pushContent($content);
return $this;
}
}
}
$GLOBALS['BlockParser'] = new BlockParser(array('Block_dl',
'Block_list',
'Block_blockquote',
'Block_heading',
'Block_hr',
'Block_pre',
'Block_plugin',
'Block_p'));
// FIXME: This is temporary, too...
function NewTransform ($text) {
global $BlockParser;
//set_time_limit(2);
// Expand leading tabs.
// FIXME: do this better.
$text = preg_replace('/^\ *[^\ \S\n][^\S\n]*/me', "str_repeat(' ', strlen('\\0'))", $text);
assert(!preg_match('/^\ *\t/', $text));
return $BlockParser->parse($text);
}
// FIXME: bad name
function TransformRevision ($revision) {
if ($revision->get('markup') == 'new') {
return NewTransform($revision->getPackedContent());
}
else {
return do_transform($revision->getContent());
}
}
// (c-file-style: "gnu")
// Local Variables:
// mode: php
// tab-width: 8
// c-basic-offset: 4
// c-hanging-comment-ender-p: nil
// indent-tabs-mode: nil
// End:
?>