1136 lines
37 KiB
PHP
1136 lines
37 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Example: get XHTML from a given Textile-markup string ($string)
|
|
*
|
|
* $textile = new Textile;
|
|
* echo $textile->TextileThis($string);
|
|
*
|
|
*/
|
|
|
|
/*
|
|
$Id: classTextile.php 216 2006-10-17 22:31:53Z zem $
|
|
$LastChangedRevision: 216 $
|
|
*/
|
|
|
|
/*
|
|
|
|
_____________
|
|
T E X T I L E
|
|
|
|
A Humane Web Text Generator
|
|
|
|
Version 2.0
|
|
|
|
Copyright (c) 2003-2004, Dean Allen <dean@textism.com>
|
|
All rights reserved.
|
|
|
|
Thanks to Carlo Zottmann <carlo@g-blog.net> for refactoring
|
|
Textile's procedural code into a class framework
|
|
|
|
Additions and fixes Copyright (c) 2006 Alex Shiels http://thresholdstate.com/
|
|
|
|
_____________
|
|
L I C E N S E
|
|
|
|
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.
|
|
|
|
_________
|
|
U S A G E
|
|
|
|
Block modifier syntax:
|
|
|
|
Header: h(1-6).
|
|
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>
|
|
|
|
Blockquote: bq.
|
|
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>
|
|
|
|
Footnote: fn(1-100).
|
|
Example: fn1. Footnote... -> <p id="fn1">Footnote...</p>
|
|
|
|
Numeric list: #, ##
|
|
Consecutive paragraphs beginning with # are wrapped in ordered list tags.
|
|
Example: <ol><li>ordered list</li></ol>
|
|
|
|
Bulleted list: *, **
|
|
Consecutive paragraphs beginning with * are wrapped in unordered list tags.
|
|
Example: <ul><li>unordered list</li></ul>
|
|
|
|
Phrase modifier syntax:
|
|
|
|
_emphasis_ -> <em>emphasis</em>
|
|
__italic__ -> <i>italic</i>
|
|
*strong* -> <strong>strong</strong>
|
|
**bold** -> <b>bold</b>
|
|
??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>
|
|
|
|
|
|
Table syntax:
|
|
|
|
Simple tables:
|
|
|
|
|a|simple|table|row|
|
|
|And|Another|table|row|
|
|
|
|
|_. A|_. table|_. header|_.row|
|
|
|A|simple|table|row|
|
|
|
|
Tables with attributes:
|
|
|
|
table{border:1px solid black}.
|
|
{background:#ddd;color:red}. |{}| | | |
|
|
|
|
|
|
Applying Attributes:
|
|
|
|
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">
|
|
# big <li>one</li>
|
|
# list <li>big</li>
|
|
<li>list</li>
|
|
</ol>
|
|
|
|
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>
|
|
|
|
*/
|
|
|
|
// define these before including this file to override the standard glyphs
|
|
@define('txt_quote_single_open', '‘');
|
|
@define('txt_quote_single_close', '’');
|
|
@define('txt_quote_double_open', '“');
|
|
@define('txt_quote_double_close', '”');
|
|
@define('txt_apostrophe', '’');
|
|
@define('txt_prime', '′');
|
|
@define('txt_prime_double', '″');
|
|
@define('txt_ellipsis', '…');
|
|
@define('txt_emdash', '—');
|
|
@define('txt_endash', '–');
|
|
@define('txt_dimension', '×');
|
|
@define('txt_trademark', '™');
|
|
@define('txt_registered', '®');
|
|
@define('txt_copyright', '©');
|
|
|
|
class Textile
|
|
{
|
|
var $hlgn;
|
|
var $vlgn;
|
|
var $clas;
|
|
var $lnge;
|
|
var $styl;
|
|
var $cspn;
|
|
var $rspn;
|
|
var $a;
|
|
var $s;
|
|
var $c;
|
|
var $pnct;
|
|
var $rel;
|
|
var $fn;
|
|
|
|
var $shelf = array();
|
|
var $restricted = false;
|
|
var $noimage = false;
|
|
var $lite = false;
|
|
var $url_schemes = array();
|
|
var $glyph = array();
|
|
var $hu = '';
|
|
|
|
var $ver = '2.0.0';
|
|
var $rev = '$Rev: 216 $';
|
|
|
|
// -------------------------------------------------------------
|
|
function Textile()
|
|
{
|
|
$this->hlgn = "(?:\<(?!>)|(?<!<)\>|\<\>|\=|[()]+(?! ))";
|
|
$this->vlgn = "[\-^~]";
|
|
$this->clas = "(?:\([^)]+\))";
|
|
$this->lnge = "(?:\[[^]]+\])";
|
|
$this->styl = "(?:\{[^}]+\})";
|
|
$this->cspn = "(?:\\\\\d+)";
|
|
$this->rspn = "(?:\/\d+)";
|
|
$this->a = "(?:{$this->hlgn}|{$this->vlgn})*";
|
|
$this->s = "(?:{$this->cspn}|{$this->rspn})*";
|
|
$this->c = "(?:{$this->clas}|{$this->styl}|{$this->lnge}|{$this->hlgn})*";
|
|
|
|
$this->pnct = '[\!"#\$%&\'()\*\+,\-\./:;<=>\?@\[\\\]\^_`{\|}\~]';
|
|
$this->urlch = '[\w"$\-_.+!*\'(),";\/?:@=&%#{}|\\^~\[\]`]';
|
|
|
|
$this->url_schemes = array('http','https','ftp','mailto');
|
|
|
|
$this->btag = array('bq', 'bc', 'notextile', 'pre', 'h[1-6]', 'fn\d+', 'p');
|
|
|
|
$this->glyph = array(
|
|
'quote_single_open' => txt_quote_single_open,
|
|
'quote_single_close' => txt_quote_single_close,
|
|
'quote_double_open' => txt_quote_double_open,
|
|
'quote_double_close' => txt_quote_double_close,
|
|
'apostrophe' => txt_apostrophe,
|
|
'prime' => txt_prime,
|
|
'prime_double' => txt_prime_double,
|
|
'ellipsis' => txt_ellipsis,
|
|
'emdash' => txt_emdash,
|
|
'endash' => txt_endash,
|
|
'dimension' => txt_dimension,
|
|
'trademark' => txt_trademark,
|
|
'registered' => txt_registered,
|
|
'copyright' => txt_copyright,
|
|
);
|
|
|
|
if (defined('hu'))
|
|
$this->hu = hu;
|
|
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function TextileThis($text, $lite='', $encode='', $noimage='', $strict='', $rel='')
|
|
{
|
|
if ($rel)
|
|
$this->rel = ' rel="'.$rel.'" ';
|
|
$this->lite = $lite;
|
|
$this->noimage = $noimage;
|
|
|
|
if ($encode) {
|
|
$text = $this->incomingEntities($text);
|
|
$text = str_replace("x%x%", "&", $text);
|
|
return $text;
|
|
} else {
|
|
|
|
if(!$strict) {
|
|
$text = $this->cleanWhiteSpace($text);
|
|
}
|
|
|
|
$text = $this->getRefs($text);
|
|
|
|
if (!$lite) {
|
|
$text = $this->block($text);
|
|
}
|
|
|
|
$text = $this->retrieve($text);
|
|
|
|
// just to be tidy
|
|
$text = str_replace("<br />", "<br />\n", $text);
|
|
|
|
return $text;
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function TextileRestricted($text, $lite=1, $noimage=1, $rel='nofollow')
|
|
{
|
|
$this->restricted = true;
|
|
$this->lite = $lite;
|
|
$this->noimage = $noimage;
|
|
if ($rel)
|
|
$this->rel = ' rel="'.$rel.'" ';
|
|
|
|
// escape any raw html
|
|
$text = $this->encode_html($text, 0);
|
|
|
|
$text = $this->cleanWhiteSpace($text);
|
|
$text = $this->getRefs($text);
|
|
|
|
if ($lite) {
|
|
$text = $this->blockLite($text);
|
|
}
|
|
else {
|
|
$text = $this->block($text);
|
|
}
|
|
|
|
$text = $this->retrieve($text);
|
|
|
|
// just to be tidy
|
|
$text = str_replace("<br />", "<br />\n", $text);
|
|
|
|
return $text;
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function pba($in, $element = "") // "parse block attributes"
|
|
{
|
|
$style = '';
|
|
$class = '';
|
|
$lang = '';
|
|
$colspan = '';
|
|
$rowspan = '';
|
|
$id = '';
|
|
$atts = '';
|
|
|
|
if (!empty($in)) {
|
|
$matched = $in;
|
|
if ($element == 'td') {
|
|
if (preg_match("/\\\\(\d+)/", $matched, $csp)) $colspan = $csp[1];
|
|
if (preg_match("/\/(\d+)/", $matched, $rsp)) $rowspan = $rsp[1];
|
|
}
|
|
|
|
if ($element == 'td' or $element == 'tr') {
|
|
if (preg_match("/($this->vlgn)/", $matched, $vert))
|
|
$style[] = "vertical-align:" . $this->vAlign($vert[1]) . ";";
|
|
}
|
|
|
|
if (preg_match("/\{([^}]*)\}/", $matched, $sty)) {
|
|
$style[] = rtrim($sty[1], ';') . ';';
|
|
$matched = str_replace($sty[0], '', $matched);
|
|
}
|
|
|
|
if (preg_match("/\[([^]]+)\]/U", $matched, $lng)) {
|
|
$lang = $lng[1];
|
|
$matched = str_replace($lng[0], '', $matched);
|
|
}
|
|
|
|
if (preg_match("/\(([^()]+)\)/U", $matched, $cls)) {
|
|
$class = $cls[1];
|
|
$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)) {
|
|
// $this->dump($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)) {
|
|
$id = $ids[2];
|
|
$class = $ids[1];
|
|
}
|
|
|
|
if ($this->restricted)
|
|
return ($lang) ? ' lang="' . $lang .'"':'';
|
|
|
|
return join('',array(
|
|
($style) ? ' style="' . join("", $style) .'"':'',
|
|
($class) ? ' class="' . $class .'"':'',
|
|
($lang) ? ' lang="' . $lang .'"':'',
|
|
($id) ? ' id="' . $id .'"':'',
|
|
($colspan) ? ' colspan="' . $colspan .'"':'',
|
|
($rowspan) ? ' rowspan="' . $rowspan .'"':''
|
|
));
|
|
}
|
|
return '';
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function hasRawText($text)
|
|
{
|
|
// checks whether the text has text not already enclosed by a block tag
|
|
$r = trim(preg_replace('@<(p|blockquote|div|form|table|ul|ol|pre|h\d)[^>]*?>.*</\1>@s', '', trim($text)));
|
|
$r = trim(preg_replace('@<(hr|br)[^>]*?/>@', '', $r));
|
|
return '' != $r;
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function table($text)
|
|
{
|
|
$text = $text . "\n\n";
|
|
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", ltrim($row), $rmtch)) {
|
|
$ratts = $this->pba($rmtch[1], 'tr');
|
|
$row = $rmtch[2];
|
|
} else $ratts = '';
|
|
|
|
$cells = array();
|
|
foreach(explode("|", $row) as $cell) {
|
|
$ctyp = "d";
|
|
if (preg_match("/^_/", $cell)) $ctyp = "h";
|
|
if (preg_match("/^(_?$this->s$this->a$this->c\. )(.*)/", $cell, $cmtch)) {
|
|
$catts = $this->pba($cmtch[1], 'td');
|
|
$cell = $cmtch[2];
|
|
} else $catts = '';
|
|
|
|
$cell = $this->graf($this->span($cell));
|
|
|
|
if (trim($cell) != '')
|
|
$cells[] = "\t\t\t<t$ctyp$catts>$cell</t$ctyp>";
|
|
}
|
|
$rows[] = "\t\t<tr$ratts>\n" . join("\n", $cells) . ($cells ? "\n" : "") . "\t\t</tr>";
|
|
unset($cells, $catts);
|
|
}
|
|
return "\t<table$tatts>\n" . join("\n", $rows) . "\n\t</table>\n\n";
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function lists($text)
|
|
{
|
|
return preg_replace_callback("/^([#*]+$this->c .*)$(?![^#*])/smU", array(&$this, "fList"), $text);
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function fList($m)
|
|
{
|
|
$text = explode("\n", $m[0]);
|
|
foreach($text as $nr => $line) {
|
|
$nextline = isset($text[$nr+1]) ? $text[$nr+1] : false;
|
|
if (preg_match("/^([#*]+)($this->a$this->c) (.*)$/s", $line, $m)) {
|
|
list(, $tl, $atts, $content) = $m;
|
|
$nl = '';
|
|
if (preg_match("/^([#*]+)\s.*/", $nextline, $nm))
|
|
$nl = $nm[1];
|
|
if (!isset($lists[$tl])) {
|
|
$lists[$tl] = true;
|
|
$atts = $this->pba($atts);
|
|
$line = "\t<" . $this->lT($tl) . "l$atts>\n\t\t<li>" . $this->graf($content);
|
|
} else {
|
|
$line = "\t\t<li>" . $this->graf($content);
|
|
}
|
|
|
|
if(strlen($nl) <= strlen($tl)) $line .= "</li>";
|
|
foreach(array_reverse($lists) as $k => $v) {
|
|
if(strlen($k) > strlen($nl)) {
|
|
$line .= "\n\t</" . $this->lT($k) . "l>";
|
|
if(strlen($k) > 1)
|
|
$line .= "</li>";
|
|
unset($lists[$k]);
|
|
}
|
|
}
|
|
}
|
|
$out[] = $line;
|
|
}
|
|
return join("\n", $out);
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function lT($in)
|
|
{
|
|
return preg_match("/^#+/", $in) ? 'o' : 'u';
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function doPBr($in)
|
|
{
|
|
return preg_replace_callback('@<(p)([^>]*?)>(.*)(</\1>)@s', array(&$this, 'doBr'), $in);
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function doBr($m)
|
|
{
|
|
$content = preg_replace("@(.+)(?<!<br>|<br />)\n(?![#*\s|])@", '$1<br />', $m[3]);
|
|
return '<'.$m[1].$m[2].'>'.$content.$m[4];
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function block($text)
|
|
{
|
|
$find = $this->btag;
|
|
$tre = join('|', $find);
|
|
|
|
$text = explode("\n\n", $text);
|
|
|
|
$tag = 'p';
|
|
$atts = $cite = $graf = $ext = '';
|
|
|
|
foreach($text as $line) {
|
|
$anon = 0;
|
|
if (preg_match("/^($tre)($this->a$this->c)\.(\.?)(?::(\S+))? (.*)$/s", $line, $m)) {
|
|
// last block was extended, so close it
|
|
if ($ext)
|
|
$out[count($out)-1] .= $c1;
|
|
// new block
|
|
list(,$tag,$atts,$ext,$cite,$graf) = $m;
|
|
list($o1, $o2, $content, $c2, $c1) = $this->fBlock(array(0,$tag,$atts,$ext,$cite,$graf));
|
|
|
|
// leave off c1 if this block is extended, we'll close it at the start of the next block
|
|
if ($ext)
|
|
$line = $o1.$o2.$content.$c2;
|
|
else
|
|
$line = $o1.$o2.$content.$c2.$c1;
|
|
}
|
|
else {
|
|
// anonymous block
|
|
$anon = 1;
|
|
if ($ext or !preg_match('/^ /', $line)) {
|
|
list($o1, $o2, $content, $c2, $c1) = $this->fBlock(array(0,$tag,$atts,$ext,$cite,$line));
|
|
// skip $o1/$c1 because this is part of a continuing extended block
|
|
if ($tag == 'p' and !$this->hasRawText($content)) {
|
|
$line = $content;
|
|
}
|
|
else {
|
|
$line = $o2.$content.$c2;
|
|
}
|
|
}
|
|
else {
|
|
$line = $this->graf($line);
|
|
}
|
|
}
|
|
|
|
$line = $this->doPBr($line);
|
|
$line = preg_replace('/<br>/', '<br />', $line);
|
|
|
|
if ($ext and $anon)
|
|
$out[count($out)-1] .= "\n".$line;
|
|
else
|
|
$out[] = $line;
|
|
|
|
if (!$ext) {
|
|
$tag = 'p';
|
|
$atts = '';
|
|
$cite = '';
|
|
$graf = '';
|
|
}
|
|
}
|
|
if ($ext) $out[count($out)-1] .= $c1;
|
|
return join("\n\n", $out);
|
|
}
|
|
|
|
|
|
|
|
// -------------------------------------------------------------
|
|
function fBlock($m)
|
|
{
|
|
// $this->dump($m);
|
|
list(, $tag, $atts, $ext, $cite, $content) = $m;
|
|
$atts = $this->pba($atts);
|
|
|
|
$o1 = $o2 = $c2 = $c1 = '';
|
|
|
|
if (preg_match("/fn(\d+)/", $tag, $fns)) {
|
|
$tag = 'p';
|
|
$fnid = empty($this->fn[$fns[1]]) ? $fns[1] : $this->fn[$fns[1]];
|
|
$atts .= ' id="fn' . $fnid . '"';
|
|
if (strpos($atts, 'class=') === false)
|
|
$atts .= ' class="footnote"';
|
|
$content = '<sup>' . $fns[1] . '</sup> ' . $content;
|
|
}
|
|
|
|
if ($tag == "bq") {
|
|
$cite = $this->checkRefs($cite);
|
|
$cite = ($cite != '') ? ' cite="' . $cite . '"' : '';
|
|
$o1 = "\t<blockquote$cite$atts>\n";
|
|
$o2 = "\t\t<p$atts>";
|
|
$c2 = "</p>";
|
|
$c1 = "\n\t</blockquote>";
|
|
}
|
|
elseif ($tag == 'bc') {
|
|
$o1 = "<pre$atts>";
|
|
$o2 = "<code$atts>";
|
|
$c2 = "</code>";
|
|
$c1 = "</pre>";
|
|
$content = $this->shelve($this->encode_html(rtrim($content, "\n")."\n"));
|
|
}
|
|
elseif ($tag == 'notextile') {
|
|
$content = $this->shelve($content);
|
|
$o1 = $o2 = '';
|
|
$c1 = $c2 = '';
|
|
}
|
|
elseif ($tag == 'pre') {
|
|
$content = $this->shelve($this->encode_html(rtrim($content, "\n")."\n"));
|
|
$o1 = "<pre$atts>";
|
|
$o2 = $c2 = '';
|
|
$c1 = "</pre>";
|
|
}
|
|
else {
|
|
$o2 = "\t<$tag$atts>";
|
|
$c2 = "</$tag>";
|
|
}
|
|
|
|
$content = $this->graf($content);
|
|
|
|
return array($o1, $o2, $content, $c2, $c1);
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function graf($text)
|
|
{
|
|
// handle normal paragraph text
|
|
if (!$this->lite) {
|
|
$text = $this->noTextile($text);
|
|
$text = $this->code($text);
|
|
}
|
|
|
|
$text = $this->links($text);
|
|
if (!$this->noimage)
|
|
$text = $this->image($text);
|
|
|
|
if (!$this->lite) {
|
|
$text = $this->lists($text);
|
|
$text = $this->table($text);
|
|
}
|
|
|
|
$text = $this->span($text);
|
|
$text = $this->footnoteRef($text);
|
|
$text = $this->glyphs($text);
|
|
return rtrim($text, "\n");
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function span($text)
|
|
{
|
|
$qtags = array('\*\*','\*','\?\?','-','__','_','%','\+','~','\^');
|
|
$pnct = ".,\"'?!;:";
|
|
|
|
foreach($qtags as $f) {
|
|
$text = preg_replace_callback("/
|
|
(?:^|(?<=[\s>$pnct])|([{[]))
|
|
($f)(?!$f)
|
|
({$this->c})
|
|
(?::(\S+))?
|
|
([^\s$f]+|\S[^$f\n]*[^\s$f\n])
|
|
([$pnct]*)
|
|
$f
|
|
(?:$|([\]}])|(?=[[:punct:]]{1,2}|\s))
|
|
/x", array(&$this, "fSpan"), $text);
|
|
}
|
|
return $text;
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function fSpan($m)
|
|
{
|
|
$qtags = array(
|
|
'*' => 'strong',
|
|
'**' => 'b',
|
|
'??' => 'cite',
|
|
'_' => 'em',
|
|
'__' => 'i',
|
|
'-' => 'del',
|
|
'%' => 'span',
|
|
'+' => 'ins',
|
|
'~' => 'sub',
|
|
'^' => 'sup',
|
|
);
|
|
|
|
list(,, $tag, $atts, $cite, $content, $end) = $m;
|
|
$tag = $qtags[$tag];
|
|
$atts = $this->pba($atts);
|
|
$atts .= ($cite != '') ? 'cite="' . $cite . '"' : '';
|
|
|
|
$out = "<$tag$atts>$content$end</$tag>";
|
|
|
|
// $this->dump($out);
|
|
|
|
return $out;
|
|
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function links($text)
|
|
{
|
|
return preg_replace_callback('/
|
|
(?:^|(?<=[\s>.$pnct\(])|([{[])) # $pre
|
|
" # start
|
|
(' . $this->c . ') # $atts
|
|
([^"]+) # $text
|
|
\s?
|
|
(?:\(([^)]+)\)(?="))? # $title
|
|
":
|
|
('.$this->urlch.'+) # $url
|
|
(\/)? # $slash
|
|
([^\w\/;]*) # $post
|
|
(?:([\]}])|(?=\s|$|\)))
|
|
/Ux', array(&$this, "fLink"), $text);
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function fLink($m)
|
|
{
|
|
list(, $pre, $atts, $text, $title, $url, $slash, $post) = $m;
|
|
|
|
$url = $this->checkRefs($url);
|
|
|
|
$atts = $this->pba($atts);
|
|
$atts .= ($title != '') ? ' title="' . $this->encode_html($title) . '"' : '';
|
|
|
|
if (!$this->noimage)
|
|
$text = $this->image($text);
|
|
|
|
$text = $this->span($text);
|
|
$text = $this->glyphs($text);
|
|
|
|
$url = $this->relURL($url);
|
|
|
|
$out = '<a href="' . $this->encode_html($url . $slash) . '"' . $atts . $this->rel . '>' . $text . '</a>' . $post;
|
|
|
|
// $this->dump($out);
|
|
return $this->shelve($out);
|
|
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function getRefs($text)
|
|
{
|
|
return preg_replace_callback("/(?<=^|\s)\[(.+)\]((?:http:\/\/|\/)\S+)(?=\s|$)/U",
|
|
array(&$this, "refs"), $text);
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function refs($m)
|
|
{
|
|
list(, $flag, $url) = $m;
|
|
$this->urlrefs[$flag] = $url;
|
|
return '';
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function checkRefs($text)
|
|
{
|
|
return (isset($this->urlrefs[$text])) ? $this->urlrefs[$text] : $text;
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function relURL($url)
|
|
{
|
|
$parts = parse_url($url);
|
|
if ((empty($parts['scheme']) or @$parts['scheme'] == 'http') and
|
|
empty($parts['host']) and
|
|
preg_match('/^\w/', @$parts['path']))
|
|
$url = $this->hu.$url;
|
|
if ($this->restricted and !empty($parts['scheme']) and
|
|
!in_array($parts['scheme'], $this->url_schemes))
|
|
return '#';
|
|
return $url;
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function image($text)
|
|
{
|
|
return preg_replace_callback("/
|
|
(?:[[{])? # pre
|
|
\! # opening !
|
|
(\<|\=|\>)?? # optional alignment atts
|
|
($this->c) # optional style,class atts
|
|
(?:\. )? # optional dot-space
|
|
([^\s(!]+) # presume this is the src
|
|
\s? # optional space
|
|
(?:\(([^\)]+)\))? # optional title
|
|
\! # closing
|
|
(?::(\S+))? # optional href
|
|
(?:[\]}]|(?=\s|$)) # lookahead: space or end of string
|
|
/Ux", array(&$this, "fImage"), $text);
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function fImage($m)
|
|
{
|
|
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]) : '';
|
|
$url = $this->checkRefs($url);
|
|
|
|
$url = $this->relURL($url);
|
|
|
|
$out = array(
|
|
($href) ? '<a href="' . $href . '">' : '',
|
|
'<img src="' . $url . '"' . $atts . ' />',
|
|
($href) ? '</a>' : ''
|
|
);
|
|
|
|
return join('',$out);
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function code($text)
|
|
{
|
|
$text = $this->doSpecial($text, '<code>', '</code>', 'fCode');
|
|
$text = $this->doSpecial($text, '@', '@', 'fCode');
|
|
$text = $this->doSpecial($text, '<pre>', '</pre>', 'fPre');
|
|
return $text;
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function fCode($m)
|
|
{
|
|
@list(, $before, $text, $after) = $m;
|
|
if ($this->restricted)
|
|
// $text is already escaped
|
|
return $before.$this->shelve('<code>'.$text.'</code>').$after;
|
|
else
|
|
return $before.$this->shelve('<code>'.$this->encode_html($text).'</code>').$after;
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function fPre($m)
|
|
{
|
|
@list(, $before, $text, $after) = $m;
|
|
if ($this->restricted)
|
|
// $text is already escaped
|
|
return $before.'<pre>'.$this->shelve($text).'</pre>'.$after;
|
|
else
|
|
return $before.'<pre>'.$this->shelve($this->encode_html($text)).'</pre>'.$after;
|
|
}
|
|
// -------------------------------------------------------------
|
|
function shelve($val)
|
|
{
|
|
$i = uniqid(rand());
|
|
$this->shelf[$i] = $val;
|
|
return $i;
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function retrieve($text)
|
|
{
|
|
if (is_array($this->shelf))
|
|
do {
|
|
$old = $text;
|
|
$text = strtr($text, $this->shelf);
|
|
} while ($text != $old);
|
|
|
|
return $text;
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
// NOTE: deprecated
|
|
function incomingEntities($text)
|
|
{
|
|
return preg_replace("/&(?![#a-z0-9]+;)/i", "x%x%", $text);
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
// NOTE: deprecated
|
|
function encodeEntities($text)
|
|
{
|
|
return (function_exists('mb_encode_numericentity'))
|
|
? $this->encode_high($text)
|
|
: htmlentities($text, ENT_NOQUOTES, "utf-8");
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
// NOTE: deprecated
|
|
function fixEntities($text)
|
|
{
|
|
/* de-entify any remaining angle brackets or ampersands */
|
|
return str_replace(array(">", "<", "&"),
|
|
array(">", "<", "&"), $text);
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function cleanWhiteSpace($text)
|
|
{
|
|
$out = str_replace("\r\n", "\n", $text);
|
|
$out = preg_replace("/\n{3,}/", "\n\n", $out);
|
|
$out = preg_replace("/\n *\n/", "\n\n", $out);
|
|
$out = preg_replace('/"$/', "\" ", $out);
|
|
return $out;
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function doSpecial($text, $start, $end, $method='fSpecial')
|
|
{
|
|
return preg_replace_callback('/(^|\s|[[({>])'.preg_quote($start, '/').'(.*?)'.preg_quote($end, '/').'(\s|$|[\])}])?/ms',
|
|
array(&$this, $method), $text);
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function fSpecial($m)
|
|
{
|
|
// A special block like notextile or code
|
|
@list(, $before, $text, $after) = $m;
|
|
return $before.$this->shelve($this->encode_html($text)).$after;
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function noTextile($text)
|
|
{
|
|
$text = $this->doSpecial($text, '<notextile>', '</notextile>', 'fTextile');
|
|
return $this->doSpecial($text, '==', '==', 'fTextile');
|
|
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function fTextile($m)
|
|
{
|
|
@list(, $before, $notextile, $after) = $m;
|
|
#$notextile = str_replace(array_keys($modifiers), array_values($modifiers), $notextile);
|
|
return $before.$this->shelve($notextile).$after;
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function footnoteRef($text)
|
|
{
|
|
return preg_replace('/\b\[([0-9]+)\](\s)?/Ue',
|
|
'$this->footnoteID(\'\1\',\'\2\')', $text);
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function footnoteID($id, $t)
|
|
{
|
|
if (empty($this->fn[$id]))
|
|
$this->fn[$id] = uniqid(rand());
|
|
$fnid = $this->fn[$id];
|
|
return '<sup class="footnote"><a href="#fn'.$fnid.'">'.$id.'</a></sup>'.$t;
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function glyphs($text)
|
|
{
|
|
// fix: hackish
|
|
$text = preg_replace('/"\z/', "\" ", $text);
|
|
$pnc = '[[:punct:]]';
|
|
|
|
$glyph_search = array(
|
|
'/(\w)\'(\w)/', // apostrophe's
|
|
'/(\s)\'(\d+\w?)\b(?!\')/', // back in '88
|
|
'/(\S)\'(?=\s|'.$pnc.'|<|$)/', // single closing
|
|
'/\'/', // single opening
|
|
'/(\S)\"(?=\s|'.$pnc.'|<|$)/', // double closing
|
|
'/"/', // double opening
|
|
'/\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/', // 3+ uppercase acronym
|
|
'/\b([A-Z][A-Z\'\-]+[A-Z])(?=[\s.,\)>])/', // 3+ uppercase
|
|
'/\b( )?\.{3}/', // ellipsis
|
|
'/(\s?)--(\s?)/', // em dash
|
|
'/\s-(?:\s|$)/', // en dash
|
|
'/(\d+)( ?)x( ?)(?=\d+)/', // dimension sign
|
|
'/\b ?[([]TM[])]/i', // trademark
|
|
'/\b ?[([]R[])]/i', // registered
|
|
'/\b ?[([]C[])]/i', // copyright
|
|
);
|
|
|
|
extract($this->glyph, EXTR_PREFIX_ALL, 'txt');
|
|
|
|
$glyph_replace = array(
|
|
'$1'.$txt_apostrophe.'$2', // apostrophe's
|
|
'$1'.$txt_apostrophe.'$2', // back in '88
|
|
'$1'.$txt_quote_single_close, // single closing
|
|
$txt_quote_single_open, // single opening
|
|
'$1'.$txt_quote_double_close, // double closing
|
|
$txt_quote_double_open, // double opening
|
|
'<acronym title="$2">$1</acronym>', // 3+ uppercase acronym
|
|
'<span class="caps">$1</span>', // 3+ uppercase
|
|
'$1'.$txt_ellipsis, // ellipsis
|
|
'$1'.$txt_emdash.'$2', // em dash
|
|
' '.$txt_endash.' ', // en dash
|
|
'$1$2'.$txt_dimension.'$3', // dimension sign
|
|
$txt_trademark, // trademark
|
|
$txt_registered, // registered
|
|
$txt_copyright, // copyright
|
|
);
|
|
|
|
$text = preg_split("/(<.*>)/U", $text, -1, PREG_SPLIT_DELIM_CAPTURE);
|
|
foreach($text as $line) {
|
|
if (!preg_match("/<.*>/", $line)) {
|
|
$line = preg_replace($glyph_search, $glyph_replace, $line);
|
|
}
|
|
$glyph_out[] = $line;
|
|
}
|
|
return join('', $glyph_out);
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function iAlign($in)
|
|
{
|
|
$vals = array(
|
|
'<' => 'left',
|
|
'=' => 'center',
|
|
'>' => 'right');
|
|
return (isset($vals[$in])) ? $vals[$in] : '';
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function hAlign($in)
|
|
{
|
|
$vals = array(
|
|
'<' => 'left',
|
|
'=' => 'center',
|
|
'>' => 'right',
|
|
'<>' => 'justify');
|
|
return (isset($vals[$in])) ? $vals[$in] : '';
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function vAlign($in)
|
|
{
|
|
$vals = array(
|
|
'^' => 'top',
|
|
'-' => 'middle',
|
|
'~' => 'bottom');
|
|
return (isset($vals[$in])) ? $vals[$in] : '';
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
// NOTE: deprecated
|
|
function encode_high($text, $charset = "UTF-8")
|
|
{
|
|
return mb_encode_numericentity($text, $this->cmap(), $charset);
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
// NOTE: deprecated
|
|
function decode_high($text, $charset = "UTF-8")
|
|
{
|
|
return mb_decode_numericentity($text, $this->cmap(), $charset);
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
// NOTE: deprecated
|
|
function cmap()
|
|
{
|
|
$f = 0xffff;
|
|
$cmap = array(
|
|
0x0080, 0xffff, 0, $f);
|
|
return $cmap;
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
function encode_html($str, $quotes=1)
|
|
{
|
|
$a = array(
|
|
'&' => '&',
|
|
'<' => '<',
|
|
'>' => '>',
|
|
);
|
|
if ($quotes) $a = $a + array(
|
|
"'" => ''',
|
|
'"' => '"',
|
|
);
|
|
|
|
return strtr($str, $a);
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
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 />';
|
|
|
|
return $out;
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
// NOTE: deprecated
|
|
function txtgps($thing)
|
|
{
|
|
if (isset($_POST[$thing])) {
|
|
if (get_magic_quotes_gpc()) {
|
|
return stripslashes($_POST[$thing]);
|
|
}
|
|
else {
|
|
return $_POST[$thing];
|
|
}
|
|
}
|
|
else {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
// NOTE: deprecated
|
|
function dump()
|
|
{
|
|
foreach (func_get_args() as $a)
|
|
echo "\n<pre>",(is_array($a)) ? print_r($a) : $a, "</pre>\n";
|
|
}
|
|
|
|
// -------------------------------------------------------------
|
|
|
|
function blockLite($text)
|
|
{
|
|
$this->btag = array('bq', 'p');
|
|
return $this->block($text."\n\n");
|
|
}
|
|
|
|
|
|
} // end class
|
|
|
|
?>
|