Source for file _textile.renderer.php
Documentation is available at _textile.renderer.php
* This file implements the Textile plugin for b2evolution
* A Humane Web Text Generator
* @copyright (c) 2003-2004, Dean Allen <dean@textism.com> All rights reserved.
* b2evolution - {@link http://b2evolution.net/}
* Released under GNU GPL License - {@link http://b2evolution.net/about/license.html}
if( !defined('DB_USER') ) die( 'Please, do not access this page directly.' );
require_once dirname(__FILE__
).
'/../renderer.class.php';
* Example: get XHTML from a given Textile-markup string ($string)
* $textile = new Textile;
* echo $textile->TextileThis($string);
A Humane Web Text Generator
Copyright (c) 2003-2004, Dean Allen <dean@textism.com>
Thanks to Carlo Zottmann <carlo@g-blog.net> for refactoring
Textile's procedural code into a class framework
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name Textile nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
Paragraphs beginning with 'hn. ' (where n is 1-6) are wrapped in header tags.
Example: h1. Header... -> <h1>Header...</h1>
Paragraph: p. (also applied by default)
Example: p. Text -> <p>Text</p>
Example: bq. Block quotation... -> <blockquote>Block quotation...</blockquote>
Blockquote with citation: bq.:http://citation.url
Example: bq.:http://textism.com/ Text...
-> <blockquote cite="http://textism.com">Text...</blockquote>
Example: fn1. Footnote... -> <p id="fn1">Footnote...</p>
Consecutive paragraphs beginning with # are wrapped in ordered list tags.
Example: <ol><li>ordered list</li></ol>
Consecutive paragraphs beginning with * are wrapped in unordered list tags.
Example: <ul><li>unordered list</li></ul>
_emphasis_ -> <em>emphasis</em>
__italic__ -> <i>italic</i>
*strong* -> <strong>strong</strong>
??citation?? -> <cite>citation</cite>
-deleted text- -> <del>deleted</del>
+inserted text+ -> <ins>inserted</ins>
^superscript^ -> <sup>superscript</sup>
~subscript~ -> <sub>subscript</sub>
@code@ -> <code>computer code</code>
%(bob)span% -> <span class="bob">span</span>
==notextile== -> leave text alone (do not format)
"linktext":url -> <a href="url">linktext</a>
"linktext(title)":url -> <a href="url" title="title">linktext</a>
!imageurl! -> <img src="imageurl" />
!imageurl(alt text)! -> <img src="imageurl" alt="alt text" />
!imageurl!:linkurl -> <a href="linkurl"><img src="imageurl" /></a>
ABC(Always Be Closing) -> <acronym title="Always Be Closing">ABC</acronym>
|_. A|_. table|_. header|_.row|
table{border:1px solid black}.
{background:#ddd;color:red}. |{}| | | |
Most anywhere Textile code is used, attributes such as arbitrary css style,
css classes, and ids can be applied. The syntax is fairly consistent.
The following characters quickly alter the alignment of block elements:
< -> left align ex. p<. left-aligned para
> -> right align h3>. right-aligned header 3
= -> centred h4=. centred header 4
<> -> justified p<>. justified paragraph
These will change vertical alignment in table cells:
^ -> top ex. |^. top-aligned table cell|
- -> middle |-. middle aligned|
~ -> bottom |~. bottom aligned cell|
Plain (parentheses) inserted between block syntax and the closing dot-space
indicate classes and ids:
p(hector). paragraph -> <p class="hector">paragraph</p>
p(#fluid). paragraph -> <p id="fluid">paragraph</p>
(classes and ids can be combined)
p(hector#fluid). paragraph -> <p class="hector" id="fluid">paragraph</p>
Curly {brackets} insert arbitrary css style
p{line-height:18px}. paragraph -> <p style="line-height:18px">paragraph</p>
h3{color:red}. header 3 -> <h3 style="color:red">header 3</h3>
Square [brackets] insert language attributes
p[no]. paragraph -> <p lang="no">paragraph</p>
%[fr]phrase% -> <span lang="fr">phrase</span>
Usually Textile block element syntax requires a dot and space before the block
begins, but since lists don't, they can be styled just using braces
#{color:blue} one -> <ol style="color:blue">
Using the span tag to style a phrase
It goes like this, %{color:red}the fourth the fifth%
-> It goes like this, <span style="color:red">the fourth the fifth</span>
var $name =
'Textile (beta)';
* {@internal textile_Rendererplugin::textile_Rendererplugin(-)}}
$this->short_desc =
T_('Humane Web Text Generator 2.0 beta');
// -------------------------------------------------------------
$this->hlgn =
"(?:\<(?!>)|(?<!<)\>|\<\>|\=|[()]+)";
$this->clas =
"(?:\([^)]+\))";
$this->lnge =
"(?:\[[^]]+\])";
$this->styl =
"(?:\{[^}]+\})";
$this->cspn =
"(?:\\\\\d+)";
$this->rspn =
"(?:\/\d+)";
$this->pnct =
'[\!"#\$%&\'()\*\+,\-\./:;<=>\?@\[\\\]\^_`{\|}\~]';
// -------------------------------------------------------------
* {@internal gmcode_Rendererplugin::render(-)}}
* @param string content to render (by reference) / rendered content
* @param string Output format, see {@link format_to_output()}
* @return boolean true if we can render something for the required output format
function render( & $content, $format )
if( ! parent::render( $content, $format ) )
{ // We cannot render the required format
// Original optional parameters:
// if (get_magic_quotes_gpc())
// $text = stripslashes($text);
$text =
$this->links($text);
$text = $this->image($text);
$text =
$this->span($text);
$text = $this->lists($text);
$text =
$this->table($text);
$text =
$this->block($text);
$text = preg_replace('/<\/?notextile>/', "", $text);
// turn the temp char back to an ampersand entity
$text = str_replace("x%x%", "&", $text);
$content = str_replace("<br />", "<br />\n", $text);
// -------------------------------------------------------------
function pba($in, $element = "") // "parse block attributes"
if (preg_match("/\\\\(\d+)/", $matched, $csp)) $colspan = $csp[1];
if (preg_match("/\/(\d+)/", $matched, $rsp)) $rowspan = $rsp[1];
if (preg_match("/($this->vlgn)/
", $matched, $vert))
$style[] = "vertical-align:" . $this->vAlign($vert[1]) .
";";
if (preg_match("/\{([^}]*)\}/", $matched, $sty)) {
$style[] = $sty[1] . ';';
$matched = str_replace($sty[0], '', $matched);
if (preg_match("/\[([^)]+)\]/U", $matched, $lng)) {
$matched = str_replace($lng[0], '', $matched);
if (preg_match("/\(([^()]+)\)/U", $matched, $cls)) {
$matched = str_replace($cls[0], '', $matched);
if (preg_match("/([(]+)/", $matched, $pl)) {
$style[] = "padding-left:" . strlen($pl[1]) . "em;";
$matched = str_replace($pl[0], '', $matched);
if (preg_match("/([)]+)/", $matched, $pr)) {
$style[] = "padding-right:" . strlen($pr[1]) . "em;";
$matched = str_replace($pr[0], '', $matched);
if (preg_match("/($this->hlgn)/
", $matched, $horiz))
$style[] = "text-align:" . $this->hAlign($horiz[1]) .
";";
if (preg_match("/^(.*)#(.*)$/", $class, $ids)) {
($style) ? ' style="' . join("", $style) .'"':'',
($class) ? ' class="' . $class .'"':'',
($lang) ? ' lang="' . $lang .'"':'',
($id) ? ' id="' . $id .'"':'',
($colspan) ? ' colspan="' . $colspan .'"':'',
($rowspan) ? ' rowspan="' . $rowspan .'"':''
// -------------------------------------------------------------
return preg_replace_callback("/^(?:table(_?{
$this->s}{
$this->a}{
$this->c})\. ?\n)?^({
$this->a}{
$this->c}\.? ?\|.*\|)\n\n/smU
",
array(&$this, "fTable"), $text);
// -------------------------------------------------------------
function fTable($matches)
$tatts = $this->pba($matches[1], 'table');
foreach(preg_split("/\|$/m", $matches[2], -
1, PREG_SPLIT_NO_EMPTY) as $row) {
if (preg_match("/^($this->a$this->c\. )(.*)/m
", $row, $rmtch)) {
$ratts = $this->pba($rmtch[1], 'tr');
foreach(explode("|", $row) as $cell) {
if (preg_match("/^_/", $cell)) $ctyp = "h";
if (preg_match("/^(_?$this->s$this->a$this->c\. )(.*)/
", $cell, $cmtch)) {
$catts = $this->pba($cmtch[1], 'td');
$cells[] = "\t\t\t<t$ctyp$catts>$cell</t$ctyp>";
$rows[] = "\t\t<tr$ratts>\n" . join("\n", $cells) . "\n\t\t</tr>";
return "\t<table$tatts>\n" . join("\n", $rows) . "\n\t</table>\n\n";
// -------------------------------------------------------------
return preg_replace_callback("/^([#*]+$this->c .*)$(?![^#*])/smU
", array(&$this, "fList"), $text);
// -------------------------------------------------------------
$text = explode("\n", $m[0]);
foreach($text as $line) {
if (preg_match("/^([#*]+)($this->a$this->c) (.*)$/s
", $line, $m)) {
list(, $tl, $atts, $content) = $m;
$nl = preg_replace("/^([#*]+)\s.*/", "$1", $nextline);
if (!isset($lists[$tl])) {
$atts = $this->pba($atts);
$line =
"\t<" .
$this->lT($tl) .
"l$atts>\n\t<li>" . $content;
$line = "\t\t<li>" . $content;
} elseif($nl=="*" or $nl=="#") {
$line .= "</li>\n\t</".$this->lT($tl).
"l>\n\t</li>";
foreach($lists as $k => $v) {
$line .= "</li>\n\t</" . $this->lT($k) .
"l>";
// -------------------------------------------------------------
return preg_match("/^#+/", $in) ? 'o' : 'u';
// -------------------------------------------------------------
$find = array('bq', 'h[1-6]', 'fn\d+', 'p');
$text = preg_replace("/(.+)\n(?![#*\s|])/",
$text = explode("\n", $text);
foreach($text as $line) {
if (preg_match('/<pre>/i', $line)) {
? preg_replace_callback("/^($tag)($this->a$this->c)\.(?::(\S+))? (.*)$/
",
array(&$this, "fBlock"), $line)
$line = preg_replace('/^(?!\t|<\/?pre|<\/?code|$| )(.*)/', "\t<p>$1</p>", $line);
$line = ($pre == true) ? str_replace("<br />", "\n", $line):$line;
if (preg_match('/<\/pre>/i', $line)) {
// -------------------------------------------------------------
list(, $tag, $atts, $cite, $content) = $m;
$atts = $this->pba($atts);
if (preg_match("/fn(\d+)/", $tag, $fns)) {
$atts .= ' id="fn' . $fns[1] . '"';
$content = '<sup>' . $fns[1] . '</sup> ' . $content;
$cite =
($cite !=
'') ?
' cite="' .
$cite .
'"' :
'';
$start =
"\t<blockquote$cite>\n\t\t<p";
$end = "</p>\n\t</blockquote>";
return "$start$atts>$content$end";
// -------------------------------------------------------------
$qtags = array('\*','\*\*','\?\?','-','__','_','%','\+','~');
$text = preg_replace_callback("/
(?<=^|\s|[[:punct:]]|[{([])
(?=[])}]|[[:punct:]]+|\s|$)
/xmU
", array(&$this, "fSpan"), $text);
// -------------------------------------------------------------
list(, $tag, $atts, $cite, $content, $end) = $m;
$atts = $this->pba($atts);
$atts .=
($cite !=
'') ?
'cite="' .
$cite .
'"' :
'';
$out =
"<$tag$atts>$content$end</$tag>";
// -------------------------------------------------------------
return preg_replace_callback('/
([\s[{(]|[[:punct:]])? # $pre
(' . $this->c .
') # $atts
(?:\(([^)]+)\)(?="))? # $title
/Ux', array(&$this, "fLink"), $text);
// -------------------------------------------------------------
list(, $pre, $atts, $text, $title, $url, $slash, $post) = $m;
$atts =
$this->pba($atts);
$atts .=
($title !=
'') ?
' title="' .
$title .
'"' :
'';
$atts =
($atts) ?
$this->shelve($atts) :
'';
$out =
$pre .
'<a href="' .
$url .
$slash .
'"' .
$atts .
'>' .
$text .
'</a>' .
$post;
// -------------------------------------------------------------
return preg_replace_callback("/(?<=^|\s)\[(.+)\]((?:http:\/\/|\/)\S+)(?=\s|$)/U",
array(&$this, "refs"), $text);
// -------------------------------------------------------------
list(, $flag, $url) = $m;
$this->urlrefs[$flag] =
$url;
// -------------------------------------------------------------
function checkRefs($text)
return (isset($this->urlrefs[$text])) ?
$this->urlrefs[$text] :
$text;
// -------------------------------------------------------------
return preg_replace_callback("/
(\<|\=|\>)? # optional alignment atts
($this->c) # optional style,class atts
(?:\. )? # optional dot-space
([^\s(!]+) # presume this is the src
(?:\(([^\)]+)\))? # optional title
(?::(\S+))? # optional href
(?=\s|$) # lookahead: space or end of string
/Ux
", array(&$this, "fImage"), $text);
// -------------------------------------------------------------
list(, $algn, $atts, $url) = $m;
$atts = $this->pba($atts);
$atts .=
($algn !=
'') ?
' align="' .
$this->iAlign($algn) .
'"' :
'';
$atts .=
(isset
($m[4])) ?
' title="' .
$m[4] .
'"' :
'';
$atts .=
(isset
($m[4])) ?
' alt="' .
$m[4] .
'"' :
' alt=""';
$size =
@getimagesize($url);
if ($size) $atts .=
" $size[3]";
$href = (isset($m[5])) ? $this->checkRefs($m[5]) :
'';
($href) ?
'<a href="' .
$href .
'">' :
'',
'<img src="' .
$url .
'"' .
$atts .
' />',
// -------------------------------------------------------------
function textilecode($text)
return preg_replace_callback("/
(?:^|(?<=[\s\(])|([[{])) # before
/Ux", array(&$this, "fCode"), $text);
// -------------------------------------------------------------
@list(, $before, $lang, $code, $after) = $m;
$lang = ($lang) ? ' language="' . $lang . '"' : '';
return $before . '<code' . $lang . '>' . $code . '</code>' . $after;
// -------------------------------------------------------------
return ' <' .
count($this->shelf) .
'>';
// -------------------------------------------------------------
if (isset($this->shelf) &&
is_array($this->shelf)) {
foreach($this->shelf as $r) {
$text = str_replace("<$i>", $r, $text);
// -------------------------------------------------------------
function incomingEntities($text)
return preg_replace("/&(?![#a-z0-9]+;)/i", "x%x%", $text);
// -------------------------------------------------------------
function encodeEntities($text)
return (function_exists('mb_encode_numericentity'))
:
htmlentities($text, ENT_NOQUOTES, "utf-8");
// -------------------------------------------------------------
function fixEntities($text)
/* de-entify any remaining angle brackets or ampersands */
return str_replace(array(">", "<", "&"),
array(">", "<", "&"), $text);
// -------------------------------------------------------------
function cleanWhiteSpace($text)
$out = str_replace(array("\r\n", "\t"), array("\n", ''), $text);
$out = preg_replace("/\n{3,}/", "\n\n", $out);
$out = preg_replace("/\n *\n/", "\n\n", $out);
$out = preg_replace('/"$/', "\" ", $out);
// -------------------------------------------------------------
function noTextile($text)
return preg_replace('/(^|\s)==(.*)==(\s|$)?/msU',
'$1<notextile>$2</notextile>$3', $text);
// -------------------------------------------------------------
function superscript($text)
return preg_replace('/\^(.*)\^/mU', '<sup>$1</sup>', $text);
// -------------------------------------------------------------
function footnoteRef($text)
return preg_replace('/\b\[([0-9]+)\](\s)?/U',
'<sup><a href="#fn$1">$1</a></sup>$2', $text);
// -------------------------------------------------------------
$text = preg_replace('/"\z/', "\" ", $text);
'/([^\s[{(>_*])?\'(?(1)|(?=\s|s\b|'.$pnc.'))/', // single closing
'/\'/', // single opening
'/([^\s[{(>_*])?"(?(1)|(?=\s|'.$pnc.'))/', // double closing
'/\b( )?\.{3}/', // ellipsis
'/\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/', // 3+ uppercase acronym
'/(\d+) ?x ?(\d+)/', // dimension sign
'/\b ?[([]TM[])]/i', // trademark
'/\b ?[([]R[])]/i', // registered
'/\b ?[([]C[])]/i'); // copyright
$glyph_replace = array('$1’$2', // single closing
'‘', // single opening
'$1”', // double closing
'“', // double opening
'<acronym title="$2">$1</acronym>', // 3+ uppercase acronym
'$1×$2', // dimension sign
/* if no html, do a simple search and replace... */
if (!preg_match("/<.*>/", $text)) {
$text = preg_replace($glyph_search, $glyph_replace, $text);
$text = preg_split("/(<.*>)/U", $text, -1, PREG_SPLIT_DELIM_CAPTURE);
foreach($text as $line) {
$offtags = ('code|pre|kbd|notextile');
/* matches are off if we're between <code>, <pre> etc. */
if (preg_match('/<(' . $offtags . ')>/i', $line)) $codepre = true;
if (preg_match('/<\/(' . $offtags . ')>/i', $line)) $codepre = false;
if (!preg_match("/<.*>/", $line) && $codepre == false) {
$line = preg_replace($glyph_search, $glyph_replace, $line);
/* do htmlspecial if between <code> */
$line = htmlspecialchars($line, ENT_NOQUOTES, "UTF-8");
$line = preg_replace('/<(\/?' . $offtags . ')>/', "<$1>", $line);
return join('', $glyph_out);
// -------------------------------------------------------------
return (isset($vals[$in])) ? $vals[$in] : '';
// -------------------------------------------------------------
return (isset($vals[$in])) ? $vals[$in] : '';
// -------------------------------------------------------------
return (isset($vals[$in])) ? $vals[$in] : '';
// -------------------------------------------------------------
function encode_high($text, $charset = "UTF-8")
return mb_encode_numericentity($text, $this->cmap(), $charset);
// -------------------------------------------------------------
function decode_high($text, $charset = "UTF-8")
return mb_decode_numericentity($text, $this->cmap(), $charset);
// -------------------------------------------------------------
// -------------------------------------------------------------
function textile_popup_help($name, $helpvar, $windowW, $windowH)
return ' <a target="_blank" href="http://www.textpattern.com/help/?item=' . $helpvar . '" onclick="window.open(this.href, \'popupwindow\', \'width=' . $windowW . ',height=' . $windowH . ',scrollbars,resizable\'); return false;">' . $name . '</a><br />';
// -------------------------------------------------------------
if (isset($_POST[$thing])) {
if (get_magic_quotes_gpc()) {
return stripslashes($_POST[$thing]);
// -------------------------------------------------------------
foreach (func_get_args() as $a)
echo "\n<pre>",(is_array($a)) ? print_r($a) : $a, "</pre>\n";
$this->register( new textile_Rendererplugin() );