1
+ − 1
<?php
536
+ − 2
+ − 3
/*
+ − 4
* Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
1081
745200a9cc2a
Fixed some upgrade bugs; added support for choosing one's own date/time formats; rebrand as 1.1.7
Dan
diff
changeset
+ − 5
* Copyright (C) 2006-2009 Dan Fuhry
536
+ − 6
*
+ − 7
* This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
+ − 8
* as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
+ − 9
*
+ − 10
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ − 11
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
+ − 12
*/
+ − 13
1
+ − 14
/**
1027
+ − 15
* Framework for parsing and rendering various formats. In Enano by default, this is MediaWiki-style wikitext being
+ − 16
* rendered to XHTML, but this framework allows other formats to be supported as well.
1
+ − 17
*
1027
+ − 18
* @package Enano
+ − 19
* @subpackage Content
+ − 20
* @author Dan Fuhry <dan@enanocms.org>
+ − 21
* @copyright (C) 2009 Enano CMS Project
+ − 22
* @license GNU General Public License, version 2 or later <http://www.gnu.org/licenses/gpl-2.0.html>
1
+ − 23
*/
+ − 24
1027
+ − 25
class Carpenter
+ − 26
{
+ − 27
/**
+ − 28
* Parser token
+ − 29
* @const string
+ − 30
*/
+ − 31
+ − 32
const PARSER_TOKEN = "\xFF";
+ − 33
+ − 34
/**
+ − 35
* Parsing engine
+ − 36
* @var string
+ − 37
*/
+ − 38
+ − 39
private $parser = 'mediawiki';
+ − 40
+ − 41
/**
+ − 42
* Rendering engine
+ − 43
* @var string
+ − 44
*/
+ − 45
+ − 46
private $renderer = 'xhtml';
+ − 47
+ − 48
/**
+ − 49
* Rendering flags
+ − 50
*/
+ − 51
+ − 52
public $flags = RENDER_WIKI_DEFAULT;
+ − 53
+ − 54
/**
+ − 55
* List of rendering rules
+ − 56
* @var array
+ − 57
*/
+ − 58
+ − 59
private $rules = array(
+ − 60
'lang',
+ − 61
'templates',
1078
67a4c839c7e1
Blockquote functionality in wikitext parser now allows rendering of other block level elements properly
Dan
diff
changeset
+ − 62
'blockquote',
1027
+ − 63
'tables',
+ − 64
'heading',
+ − 65
// note: can't be named list ("list" is a PHP language construct)
+ − 66
'multilist',
+ − 67
'bold',
+ − 68
'italic',
+ − 69
'underline',
+ − 70
'externalwithtext',
+ − 71
'externalnotext',
+ − 72
'image',
+ − 73
'internallink',
1078
67a4c839c7e1
Blockquote functionality in wikitext parser now allows rendering of other block level elements properly
Dan
diff
changeset
+ − 74
'paragraph',
67a4c839c7e1
Blockquote functionality in wikitext parser now allows rendering of other block level elements properly
Dan
diff
changeset
+ − 75
'blockquotepost'
1
+ − 76
);
1027
+ − 77
+ − 78
/**
+ − 79
* List of render hooks
+ − 80
* @var array
+ − 81
*/
+ − 82
+ − 83
private $hooks = array();
+ − 84
+ − 85
/* private $rules = array('prefilter', 'delimiter', 'code', 'function', 'html', 'raw', 'include', 'embed', 'anchor',
+ − 86
'heading', 'toc', 'horiz', 'break', 'blockquote', 'list', 'deflist', 'table', 'image',
+ − 87
'phplookup', 'center', 'newline', 'paragraph', 'url', 'freelink', 'interwiki',
+ − 88
'wikilink', 'colortext', 'strong', 'bold', 'emphasis', 'italic', 'underline', 'tt',
+ − 89
'superscript', 'subscript', 'revise', 'tighten'); */
+ − 90
+ − 91
/**
+ − 92
* Render the text!
+ − 93
* @param string Text to render
+ − 94
* @return string
+ − 95
*/
+ − 96
+ − 97
public function render($text)
+ − 98
{
+ − 99
$parser_class = "Carpenter_Parse_" . ucwords($this->parser);
+ − 100
$renderer_class = "Carpenter_Render_" . ucwords($this->renderer);
+ − 101
+ − 102
// include files, if we haven't already
+ − 103
if ( !class_exists($parser_class) )
+ − 104
{
+ − 105
require_once( ENANO_ROOT . "/includes/wikiengine/parse_{$this->parser}.php");
+ − 106
}
+ − 107
+ − 108
if ( !class_exists($renderer_class) )
1
+ − 109
{
1027
+ − 110
require_once( ENANO_ROOT . "/includes/wikiengine/render_{$this->renderer}.php");
+ − 111
}
+ − 112
+ − 113
$parser = new $parser_class;
+ − 114
$renderer = new $renderer_class;
+ − 115
+ − 116
// run prehooks
+ − 117
foreach ( $this->hooks as $hook )
+ − 118
{
+ − 119
if ( $hook['when'] === PO_FIRST )
+ − 120
{
+ − 121
$text = call_user_func($hook['callback'], $text);
+ − 122
if ( !is_string($text) || empty($text) )
+ − 123
{
+ − 124
trigger_error("Hook returned empty/invalid text: " . print_r($hook['callback'], true), E_USER_WARNING);
+ − 125
// *sigh*
+ − 126
$text = '';
1
+ − 127
}
1027
+ − 128
}
+ − 129
}
+ − 130
+ − 131
// perform render
+ − 132
foreach ( $this->rules as $rule )
+ − 133
{
+ − 134
// run prehooks
+ − 135
foreach ( $this->hooks as $hook )
+ − 136
{
+ − 137
if ( $hook['when'] === PO_BEFORE && $hook['rule'] === $rule )
407
+ − 138
{
1027
+ − 139
$text = call_user_func($hook['callback'], $text);
+ − 140
if ( !is_string($text) || empty($text) )
438
+ − 141
{
1027
+ − 142
trigger_error("Hook returned empty/invalid text: " . print_r($hook['callback'], true), E_USER_WARNING);
+ − 143
// *sigh*
+ − 144
$text = '';
438
+ − 145
}
407
+ − 146
}
1
+ − 147
}
1027
+ − 148
+ − 149
// execute rule
+ − 150
$text = $this->perform_render_step($text, $rule, $parser, $renderer);
+ − 151
+ − 152
// run posthooks
+ − 153
foreach ( $this->hooks as $hook )
+ − 154
{
+ − 155
if ( $hook['when'] === PO_AFTER && $hook['rule'] === $rule )
+ − 156
{
+ − 157
$text = call_user_func($hook['callback'], $text);
+ − 158
if ( !is_string($text) || empty($text) )
+ − 159
{
+ − 160
trigger_error("Hook returned empty/invalid text: " . print_r($hook['callback'], true), E_USER_WARNING);
+ − 161
// *sigh*
+ − 162
$text = '';
1
+ − 163
}
+ − 164
}
+ − 165
}
+ − 166
}
1027
+ − 167
+ − 168
// run posthooks
+ − 169
foreach ( $this->hooks as $hook )
1
+ − 170
{
1027
+ − 171
if ( $hook['when'] === PO_LAST )
+ − 172
{
+ − 173
$text = call_user_func($hook['callback'], $text);
+ − 174
if ( !is_string($text) || empty($text) )
+ − 175
{
+ − 176
trigger_error("Hook returned empty/invalid text: " . print_r($hook['callback'], true), E_USER_WARNING);
+ − 177
// *sigh*
+ − 178
$text = '';
1
+ − 179
}
+ − 180
}
+ − 181
}
1027
+ − 182
+ − 183
return (( defined('ENANO_DEBUG') && isset($_GET['parserdebug']) ) ? '<pre>' . htmlspecialchars($text) . '</pre>' : $text) . "\n\n";
+ − 184
}
+ − 185
+ − 186
/**
+ − 187
* Performs a step in the rendering process.
+ − 188
* @param string Text to render
+ − 189
* @param string Rule to execute
+ − 190
* @param object Parser instance
+ − 191
* @param object Renderer instance
+ − 192
* @return string
+ − 193
* @access private
+ − 194
*/
+ − 195
+ − 196
private function perform_render_step($text, $rule, $parser, $renderer)
+ − 197
{
+ − 198
// First look for a direct function
+ − 199
if ( function_exists("parser_{$this->parser}_{$this->renderer}_{$rule}") )
1
+ − 200
{
1027
+ − 201
return call_user_func("parser_{$this->parser}_{$this->renderer}_{$rule}", $text, $this->flags);
1
+ − 202
}
1027
+ − 203
+ − 204
// We don't have that, so start looking for other ways or means of doing this
+ − 205
if ( method_exists($parser, $rule) && method_exists($renderer, $rule) )
+ − 206
{
+ − 207
// Both the parser and render have callbacks they want to use.
+ − 208
$pieces = $parser->$rule($text);
+ − 209
$text = call_user_func(array($renderer, $rule), $text, $pieces);
+ − 210
}
+ − 211
else if ( method_exists($parser, $rule) && !method_exists($renderer, $rule) && isset($renderer->rules[$rule]) )
1
+ − 212
{
1027
+ − 213
// The parser has a callback, but the renderer does not
+ − 214
$pieces = $parser->$rule($text);
+ − 215
$text = $this->generic_render($text, $pieces, $renderer->rules[$rule]);
1
+ − 216
}
1027
+ − 217
else if ( !method_exists($parser, $rule) && isset($parser->rules[$rule]) && method_exists($renderer, $rule) )
1
+ − 218
{
1027
+ − 219
// The parser has no callback, but the renderer does
+ − 220
$text = preg_replace_callback($parser->rules[$rule], array($renderer, $rule), $text);
+ − 221
}
+ − 222
else if ( isset($parser->rules[$rule]) && isset($renderer->rules[$rule]) )
+ − 223
{
+ − 224
// This is a straight-up regex only rule
+ − 225
$text = preg_replace($parser->rules[$rule], $renderer->rules[$rule], $text);
+ − 226
}
+ − 227
else
+ − 228
{
+ − 229
// Either the renderer or parser does not support this rule, ignore it
1
+ − 230
}
1027
+ − 231
+ − 232
return $text;
+ − 233
}
+ − 234
+ − 235
/**
+ − 236
* Generic renderer
+ − 237
* @access protected
+ − 238
*/
+ − 239
+ − 240
protected function generic_render($text, $pieces, $rule)
+ − 241
{
+ − 242
foreach ( $pieces as $i => $piece )
1
+ − 243
{
1027
+ − 244
$replacement = $rule;
+ − 245
+ − 246
// if the piece is an array, replace $1, $2, etc. in the rule with each value in the piece
+ − 247
if ( is_array($piece) )
+ − 248
{
+ − 249
$j = 0;
+ − 250
foreach ( $piece as $part )
+ − 251
{
+ − 252
$j++;
+ − 253
$replacement = str_replace(array("\\$j", "\${$j}"), $part, $replacement);
1
+ − 254
}
1027
+ − 255
}
+ − 256
// else, just replace \\1 or $1 in the rule with the piece
+ − 257
else
+ − 258
{
+ − 259
$replacement = str_replace(array("\\1", "\$1"), $piece, $replacement);
+ − 260
}
+ − 261
+ − 262
$text = str_replace(self::generate_token($i), $replacement, $text);
1
+ − 263
}
1027
+ − 264
+ − 265
return $text;
+ − 266
}
+ − 267
+ − 268
/**
+ − 269
* Add a hook into the parser.
+ − 270
* @param callback Function to call
+ − 271
* @param int PO_* constant
+ − 272
* @param string If PO_{BEFORE,AFTER} used, rule
+ − 273
*/
+ − 274
+ − 275
public function hook($callback, $when, $rule = false)
+ − 276
{
+ − 277
if ( !is_int($when) )
+ − 278
return null;
+ − 279
if ( ($when == PO_BEFORE || $when == PO_AFTER) && !is_string($rule) )
+ − 280
return null;
+ − 281
if ( ( is_string($callback) && !function_exists($callback) ) || ( is_array($callback) && !method_exists($callback[0], $callback[1]) ) || ( !is_string($callback) && !is_array($callback) ) )
1
+ − 282
{
1027
+ − 283
trigger_error("Attempt to hook with undefined function/method " . print_r($callback, true), E_USER_ERROR);
+ − 284
return null;
1
+ − 285
}
1027
+ − 286
+ − 287
$this->hooks[] = array(
+ − 288
'callback' => $callback,
+ − 289
'when' => $when,
+ − 290
'rule' => $rule
+ − 291
);
+ − 292
}
+ − 293
+ − 294
/**
1031
8a4b75e73137
Wiki formatting: Headings: tolerate spaces after line; added disable_rule method (required for rev. 1029)
Dan
diff
changeset
+ − 295
* Disable a render stage
8a4b75e73137
Wiki formatting: Headings: tolerate spaces after line; added disable_rule method (required for rev. 1029)
Dan
diff
changeset
+ − 296
* @param string stage
8a4b75e73137
Wiki formatting: Headings: tolerate spaces after line; added disable_rule method (required for rev. 1029)
Dan
diff
changeset
+ − 297
* @return null
8a4b75e73137
Wiki formatting: Headings: tolerate spaces after line; added disable_rule method (required for rev. 1029)
Dan
diff
changeset
+ − 298
*/
8a4b75e73137
Wiki formatting: Headings: tolerate spaces after line; added disable_rule method (required for rev. 1029)
Dan
diff
changeset
+ − 299
8a4b75e73137
Wiki formatting: Headings: tolerate spaces after line; added disable_rule method (required for rev. 1029)
Dan
diff
changeset
+ − 300
public function disable_rule($rule)
8a4b75e73137
Wiki formatting: Headings: tolerate spaces after line; added disable_rule method (required for rev. 1029)
Dan
diff
changeset
+ − 301
{
8a4b75e73137
Wiki formatting: Headings: tolerate spaces after line; added disable_rule method (required for rev. 1029)
Dan
diff
changeset
+ − 302
foreach ( $this->rules as $i => $current_rule )
8a4b75e73137
Wiki formatting: Headings: tolerate spaces after line; added disable_rule method (required for rev. 1029)
Dan
diff
changeset
+ − 303
{
8a4b75e73137
Wiki formatting: Headings: tolerate spaces after line; added disable_rule method (required for rev. 1029)
Dan
diff
changeset
+ − 304
if ( $current_rule === $rule )
8a4b75e73137
Wiki formatting: Headings: tolerate spaces after line; added disable_rule method (required for rev. 1029)
Dan
diff
changeset
+ − 305
{
8a4b75e73137
Wiki formatting: Headings: tolerate spaces after line; added disable_rule method (required for rev. 1029)
Dan
diff
changeset
+ − 306
unset($this->rules[$i]);
8a4b75e73137
Wiki formatting: Headings: tolerate spaces after line; added disable_rule method (required for rev. 1029)
Dan
diff
changeset
+ − 307
return null;
8a4b75e73137
Wiki formatting: Headings: tolerate spaces after line; added disable_rule method (required for rev. 1029)
Dan
diff
changeset
+ − 308
}
8a4b75e73137
Wiki formatting: Headings: tolerate spaces after line; added disable_rule method (required for rev. 1029)
Dan
diff
changeset
+ − 309
}
8a4b75e73137
Wiki formatting: Headings: tolerate spaces after line; added disable_rule method (required for rev. 1029)
Dan
diff
changeset
+ − 310
return null;
8a4b75e73137
Wiki formatting: Headings: tolerate spaces after line; added disable_rule method (required for rev. 1029)
Dan
diff
changeset
+ − 311
}
8a4b75e73137
Wiki formatting: Headings: tolerate spaces after line; added disable_rule method (required for rev. 1029)
Dan
diff
changeset
+ − 312
8a4b75e73137
Wiki formatting: Headings: tolerate spaces after line; added disable_rule method (required for rev. 1029)
Dan
diff
changeset
+ − 313
/**
1054
e6b14d33ac55
Renderer: added "smart paragraphs" for templates. <p><b>Foo</b> {bar}</p> where bar is multiline is basically turned into proper XHTML paragraphs.
Dan
diff
changeset
+ − 314
* Make a rule exclusive (the only one called)
e6b14d33ac55
Renderer: added "smart paragraphs" for templates. <p><b>Foo</b> {bar}</p> where bar is multiline is basically turned into proper XHTML paragraphs.
Dan
diff
changeset
+ − 315
* @param string stage
e6b14d33ac55
Renderer: added "smart paragraphs" for templates. <p><b>Foo</b> {bar}</p> where bar is multiline is basically turned into proper XHTML paragraphs.
Dan
diff
changeset
+ − 316
* @return null
e6b14d33ac55
Renderer: added "smart paragraphs" for templates. <p><b>Foo</b> {bar}</p> where bar is multiline is basically turned into proper XHTML paragraphs.
Dan
diff
changeset
+ − 317
*/
e6b14d33ac55
Renderer: added "smart paragraphs" for templates. <p><b>Foo</b> {bar}</p> where bar is multiline is basically turned into proper XHTML paragraphs.
Dan
diff
changeset
+ − 318
e6b14d33ac55
Renderer: added "smart paragraphs" for templates. <p><b>Foo</b> {bar}</p> where bar is multiline is basically turned into proper XHTML paragraphs.
Dan
diff
changeset
+ − 319
public function exclusive_rule($rule)
e6b14d33ac55
Renderer: added "smart paragraphs" for templates. <p><b>Foo</b> {bar}</p> where bar is multiline is basically turned into proper XHTML paragraphs.
Dan
diff
changeset
+ − 320
{
e6b14d33ac55
Renderer: added "smart paragraphs" for templates. <p><b>Foo</b> {bar}</p> where bar is multiline is basically turned into proper XHTML paragraphs.
Dan
diff
changeset
+ − 321
if ( is_string($rule) )
e6b14d33ac55
Renderer: added "smart paragraphs" for templates. <p><b>Foo</b> {bar}</p> where bar is multiline is basically turned into proper XHTML paragraphs.
Dan
diff
changeset
+ − 322
$this->rules = array($rule);
e6b14d33ac55
Renderer: added "smart paragraphs" for templates. <p><b>Foo</b> {bar}</p> where bar is multiline is basically turned into proper XHTML paragraphs.
Dan
diff
changeset
+ − 323
e6b14d33ac55
Renderer: added "smart paragraphs" for templates. <p><b>Foo</b> {bar}</p> where bar is multiline is basically turned into proper XHTML paragraphs.
Dan
diff
changeset
+ − 324
return null;
e6b14d33ac55
Renderer: added "smart paragraphs" for templates. <p><b>Foo</b> {bar}</p> where bar is multiline is basically turned into proper XHTML paragraphs.
Dan
diff
changeset
+ − 325
}
e6b14d33ac55
Renderer: added "smart paragraphs" for templates. <p><b>Foo</b> {bar}</p> where bar is multiline is basically turned into proper XHTML paragraphs.
Dan
diff
changeset
+ − 326
e6b14d33ac55
Renderer: added "smart paragraphs" for templates. <p><b>Foo</b> {bar}</p> where bar is multiline is basically turned into proper XHTML paragraphs.
Dan
diff
changeset
+ − 327
/**
1027
+ − 328
* Generate a token
+ − 329
* @param int Token index
+ − 330
* @return string
+ − 331
* @static
+ − 332
*/
+ − 333
+ − 334
public static function generate_token($i)
+ − 335
{
+ − 336
return self::PARSER_TOKEN . $i . self::PARSER_TOKEN;
+ − 337
}
+ − 338
+ − 339
/**
+ − 340
* Tokenize string
+ − 341
* @param string
+ − 342
* @param array Matches
+ − 343
* @return string
+ − 344
* @static
+ − 345
*/
+ − 346
+ − 347
public static function tokenize($text, $matches)
+ − 348
{
+ − 349
$matches = array_values($matches);
+ − 350
foreach ( $matches as $i => $match )
1
+ − 351
{
1027
+ − 352
$text = str_replace_once($match, self::generate_token($i), $text);
1
+ − 353
}
1027
+ − 354
+ − 355
return $text;
+ − 356
}
1
+ − 357
}
+ − 358