1<?php
2
3/**
4 * Example: get XHTML from a given Textile-markup string ($string)
5 *
6 *        $textile = new Textile;
7 *        echo $textile->TextileThis($string);
8 *
9 */
10
11/*
12$Id: classTextile.php 216 2006-10-17 22:31:53Z zem $
13$LastChangedRevision: 216 $
14*/
15
16/*
17
18_____________
19T E X T I L E
20
21A Humane Web Text Generator
22
23Version 2.0
24
25Copyright (c) 2003-2004, Dean Allen <dean@textism.com>
26All rights reserved.
27
28Thanks to Carlo Zottmann <carlo@g-blog.net> for refactoring
29Textile's procedural code into a class framework
30
31Additions and fixes Copyright (c) 2006 Alex Shiels http://thresholdstate.com/
32
33_____________
34L I C E N S E
35
36Redistribution and use in source and binary forms, with or without
37modification, are permitted provided that the following conditions are met:
38
39* Redistributions of source code must retain the above copyright notice,
40  this list of conditions and the following disclaimer.
41
42* Redistributions in binary form must reproduce the above copyright notice,
43  this list of conditions and the following disclaimer in the documentation
44  and/or other materials provided with the distribution.
45
46* Neither the name Textile nor the names of its contributors may be used to
47  endorse or promote products derived from this software without specific
48  prior written permission.
49
50THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
51AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
52IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
53ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
54LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
55CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
56SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
57INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
58CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
59ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
60POSSIBILITY OF SUCH DAMAGE.
61
62_________
63U S A G E
64
65Block modifier syntax:
66
67    Header: h(1-6).
68    Paragraphs beginning with 'hn. ' (where n is 1-6) are wrapped in header tags.
69    Example: h1. Header... -> <h1>Header...</h1>
70
71    Paragraph: p. (also applied by default)
72    Example: p. Text -> <p>Text</p>
73
74    Blockquote: bq.
75    Example: bq. Block quotation... -> <blockquote>Block quotation...</blockquote>
76
77    Blockquote with citation: bq.:http://citation.url
78    Example: bq.:http://textism.com/ Text...
79    ->  <blockquote cite="http://textism.com">Text...</blockquote>
80
81    Footnote: fn(1-100).
82    Example: fn1. Footnote... -> <p id="fn1">Footnote...</p>
83
84    Numeric list: #, ##
85    Consecutive paragraphs beginning with # are wrapped in ordered list tags.
86    Example: <ol><li>ordered list</li></ol>
87
88    Bulleted list: *, **
89    Consecutive paragraphs beginning with * are wrapped in unordered list tags.
90    Example: <ul><li>unordered list</li></ul>
91
92Phrase modifier syntax:
93
94           _emphasis_   ->   <em>emphasis</em>
95           __italic__   ->   <i>italic</i>
96             *strong*   ->   <strong>strong</strong>
97             **bold**   ->   <b>bold</b>
98         ??citation??   ->   <cite>citation</cite>
99       -deleted text-   ->   <del>deleted</del>
100      +inserted text+   ->   <ins>inserted</ins>
101        ^superscript^   ->   <sup>superscript</sup>
102          ~subscript~   ->   <sub>subscript</sub>
103               @code@   ->   <code>computer code</code>
104          %(bob)span%   ->   <span class="bob">span</span>
105
106        ==notextile==   ->   leave text alone (do not format)
107
108       "linktext":url   ->   <a href="url">linktext</a>
109 "linktext(title)":url  ->   <a href="url" title="title">linktext</a>
110
111           !imageurl!   ->   <img src="imageurl" />
112  !imageurl(alt text)!  ->   <img src="imageurl" alt="alt text" />
113    !imageurl!:linkurl  ->   <a href="linkurl"><img src="imageurl" /></a>
114
115ABC(Always Be Closing)  ->   <acronym title="Always Be Closing">ABC</acronym>
116
117
118Table syntax:
119
120    Simple tables:
121
122        |a|simple|table|row|
123        |And|Another|table|row|
124
125        |_. A|_. table|_. header|_.row|
126        |A|simple|table|row|
127
128    Tables with attributes:
129
130        table{border:1px solid black}.
131        {background:#ddd;color:red}. |{}| | | |
132
133
134Applying Attributes:
135
136    Most anywhere Textile code is used, attributes such as arbitrary css style,
137    css classes, and ids can be applied. The syntax is fairly consistent.
138
139    The following characters quickly alter the alignment of block elements:
140
141        <  ->  left align    ex. p<. left-aligned para
142        >  ->  right align       h3>. right-aligned header 3
143        =  ->  centred           h4=. centred header 4
144        <> ->  justified         p<>. justified paragraph
145
146    These will change vertical alignment in table cells:
147
148        ^  ->  top         ex. |^. top-aligned table cell|
149        -  ->  middle          |-. middle aligned|
150        ~  ->  bottom          |~. bottom aligned cell|
151
152    Plain (parentheses) inserted between block syntax and the closing dot-space
153    indicate classes and ids:
154
155        p(hector). paragraph -> <p class="hector">paragraph</p>
156
157        p(#fluid). paragraph -> <p id="fluid">paragraph</p>
158
159        (classes and ids can be combined)
160        p(hector#fluid). paragraph -> <p class="hector" id="fluid">paragraph</p>
161
162    Curly {brackets} insert arbitrary css style
163
164        p{line-height:18px}. paragraph -> <p style="line-height:18px">paragraph</p>
165
166        h3{color:red}. header 3 -> <h3 style="color:red">header 3</h3>
167
168    Square [brackets] insert language attributes
169
170        p[no]. paragraph -> <p lang="no">paragraph</p>
171
172        %[fr]phrase% -> <span lang="fr">phrase</span>
173
174    Usually Textile block element syntax requires a dot and space before the block
175    begins, but since lists don't, they can be styled just using braces
176
177        #{color:blue} one  ->  <ol style="color:blue">
178        # big                   <li>one</li>
179        # list                  <li>big</li>
180                                <li>list</li>
181                               </ol>
182
183    Using the span tag to style a phrase
184
185        It goes like this, %{color:red}the fourth the fifth%
186              -> It goes like this, <span style="color:red">the fourth the fifth</span>
187
188*/
189
190// define these before including this file to override the standard glyphs
191@define('txt_quote_single_open',  '&#8216;');
192@define('txt_quote_single_close', '&#8217;');
193@define('txt_quote_double_open',  '&#8220;');
194@define('txt_quote_double_close', '&#8221;');
195@define('txt_apostrophe',         '&#8217;');
196@define('txt_prime',              '&#8242;');
197@define('txt_prime_double',       '&#8243;');
198@define('txt_ellipsis',           '&#8230;');
199@define('txt_emdash',             '&#8212;');
200@define('txt_endash',             '&#8211;');
201@define('txt_dimension',          '&#215;');
202@define('txt_trademark',          '&#8482;');
203@define('txt_registered',         '&#174;');
204@define('txt_copyright',          '&#169;');
205
206class Textile
207{
208    var $hlgn;
209    var $vlgn;
210    var $clas;
211    var $lnge;
212    var $styl;
213    var $cspn;
214    var $rspn;
215    var $a;
216    var $s;
217    var $c;
218    var $pnct;
219    var $rel;
220    var $fn;
221
222    var $shelf = array();
223    var $restricted = false;
224    var $noimage = false;
225    var $lite = false;
226    var $url_schemes = array();
227    var $glyph = array();
228    var $hu = '';
229
230    var $ver = '2.0.0';
231    var $rev = '$Rev: 216 $';
232
233// -------------------------------------------------------------
234    function Textile()
235    {
236        $this->hlgn = "(?:\<(?!>)|(?<!<)\>|\<\>|\=|[()]+(?! ))";
237        $this->vlgn = "[\-^~]";
238        $this->clas = "(?:\([^)]+\))";
239        $this->lnge = "(?:\[[^]]+\])";
240        $this->styl = "(?:\{[^}]+\})";
241        $this->cspn = "(?:\\\\\d+)";
242        $this->rspn = "(?:\/\d+)";
243        $this->a = "(?:{$this->hlgn}|{$this->vlgn})*";
244        $this->s = "(?:{$this->cspn}|{$this->rspn})*";
245        $this->c = "(?:{$this->clas}|{$this->styl}|{$this->lnge}|{$this->hlgn})*";
246
247        $this->pnct = '[\!"#\$%&\'()\*\+,\-\./:;<=>\?@\[\\\]\^_`{\|}\~]';
248        $this->urlch = '[\w"$\-_.+!*\'(),";\/?:@=&%#{}|\\^~\[\]`]';
249
250        $this->url_schemes = array('http','https','ftp','mailto');
251
252        $this->btag = array('bq', 'bc', 'notextile', 'pre', 'h[1-6]', 'fn\d+', 'p');
253
254        $this->glyph = array(
255           'quote_single_open'  => txt_quote_single_open,
256           'quote_single_close' => txt_quote_single_close,
257           'quote_double_open'  => txt_quote_double_open,
258           'quote_double_close' => txt_quote_double_close,
259           'apostrophe'         => txt_apostrophe,
260           'prime'              => txt_prime,
261           'prime_double'       => txt_prime_double,
262           'ellipsis'           => txt_ellipsis,
263           'emdash'             => txt_emdash,
264           'endash'             => txt_endash,
265           'dimension'          => txt_dimension,
266           'trademark'          => txt_trademark,
267           'registered'         => txt_registered,
268           'copyright'          => txt_copyright,
269        );
270
271        if (defined('hu'))
272            $this->hu = hu;
273
274    }
275
276// -------------------------------------------------------------
277    function TextileThis($text, $lite='', $encode='', $noimage='', $strict='', $rel='')
278    {
279//echo "\$text is -->".$text."<--<br />";;
280        if ($rel)
281           $this->rel = ' rel="'.$rel.'" ';
282        $this->lite = $lite;
283        $this->noimage = $noimage;
284
285        if ($encode) {
286         $text = $this->incomingEntities($text);
287            $text = str_replace("x%x%", "&#38;", $text);
288            return $text;
289        } else {
290
291            if(!$strict) {
292                $text = $this->cleanWhiteSpace($text);
293            }
294
295            $text = $this->getRefs($text);
296
297            if (!$lite) {
298                $text = $this->block($text);
299            }
300
301            $text = $this->retrieve($text);
302
303                // just to be tidy
304            $text = str_replace("<br />", "<br />\n", $text);
305
306            return $text;
307        }
308    }
309
310// -------------------------------------------------------------
311    function TextileRestricted($text, $lite=1, $noimage=1, $rel='nofollow')
312    {
313        $this->restricted = true;
314        $this->lite = $lite;
315        $this->noimage = $noimage;
316        if ($rel)
317           $this->rel = ' rel="'.$rel.'" ';
318
319            // escape any raw html
320            $text = $this->encode_html($text, 0);
321
322            $text = $this->cleanWhiteSpace($text);
323            $text = $this->getRefs($text);
324
325            if ($lite) {
326                $text = $this->blockLite($text);
327            }
328            else {
329                $text = $this->block($text);
330            }
331
332            $text = $this->retrieve($text);
333
334                // just to be tidy
335            $text = str_replace("<br />", "<br />\n", $text);
336
337            return $text;
338    }
339
340// -------------------------------------------------------------
341    function pba($in, $element = "") // "parse block attributes"
342    {
343        $style = '';
344        $class = '';
345        $lang = '';
346        $colspan = '';
347        $rowspan = '';
348        $id = '';
349        $atts = '';
350
351        if (!empty($in)) {
352            $matched = $in;
353            if ($element == 'td') {
354                if (preg_match("/\\\\(\d+)/", $matched, $csp)) $colspan = $csp[1];
355                if (preg_match("/\/(\d+)/", $matched, $rsp)) $rowspan = $rsp[1];
356            }
357
358            if ($element == 'td' or $element == 'tr') {
359                if (preg_match("/($this->vlgn)/", $matched, $vert))
360                    $style[] = "vertical-align:" . $this->vAlign($vert[1]) . ";";
361            }
362
363            if (preg_match("/\{([^}]*)\}/", $matched, $sty)) {
364                $style[] = rtrim($sty[1], ';') . ';';
365                $matched = str_replace($sty[0], '', $matched);
366            }
367
368            if (preg_match("/\[([^]]+)\]/U", $matched, $lng)) {
369                $lang = $lng[1];
370                $matched = str_replace($lng[0], '', $matched);
371            }
372
373            if (preg_match("/\(([^()]+)\)/U", $matched, $cls)) {
374                $class = $cls[1];
375                $matched = str_replace($cls[0], '', $matched);
376            }
377
378            if (preg_match("/([(]+)/", $matched, $pl)) {
379                $style[] = "padding-left:" . strlen($pl[1]) . "em;";
380                $matched = str_replace($pl[0], '', $matched);
381            }
382
383            if (preg_match("/([)]+)/", $matched, $pr)) {
384                // $this->dump($pr);
385                $style[] = "padding-right:" . strlen($pr[1]) . "em;";
386                $matched = str_replace($pr[0], '', $matched);
387            }
388
389            if (preg_match("/($this->hlgn)/", $matched, $horiz))
390                $style[] = "text-align:" . $this->hAlign($horiz[1]) . ";";
391
392            if (preg_match("/^(.*)#(.*)$/", $class, $ids)) {
393                $id = $ids[2];
394                $class = $ids[1];
395            }
396
397            if ($this->restricted)
398                return ($lang)    ? ' lang="'    . $lang            .'"':'';
399
400            return join('',array(
401                ($style)   ? ' style="'   . join("", $style) .'"':'',
402                ($class)   ? ' class="'   . $class           .'"':'',
403                ($lang)    ? ' lang="'    . $lang            .'"':'',
404                ($id)      ? ' id="'      . $id              .'"':'',
405                ($colspan) ? ' colspan="' . $colspan         .'"':'',
406                ($rowspan) ? ' rowspan="' . $rowspan         .'"':''
407            ));
408        }
409        return '';
410    }
411
412// -------------------------------------------------------------
413    function hasRawText($text)
414    {
415        // checks whether the text has text not already enclosed by a block tag
416        $r = trim(preg_replace('@<(p|blockquote|div|form|table|ul|ol|pre|h\d)[^>]*?>.*</\1>@s', '', trim($text)));
417        $r = trim(preg_replace('@<(hr|br)[^>]*?/>@', '', $r));
418        return '' != $r;
419    }
420
421// -------------------------------------------------------------
422    function table($text)
423    {
424        $text = $text . "\n\n";
425        return preg_replace_callback("/^(?:table(_?{$this->s}{$this->a}{$this->c})\. ?\n)?^({$this->a}{$this->c}\.? ?\|.*\|)\n\n/smU",
426           array(&$this, "fTable"), $text);
427    }
428
429// -------------------------------------------------------------
430    function fTable($matches)
431    {
432        $tatts = $this->pba($matches[1], 'table');
433
434        foreach(preg_split("/\|$/m", $matches[2], -1, PREG_SPLIT_NO_EMPTY) as $row) {
435            if (preg_match("/^($this->a$this->c\. )(.*)/m", ltrim($row), $rmtch)) {
436                $ratts = $this->pba($rmtch[1], 'tr');
437                $row = $rmtch[2];
438            } else $ratts = '';
439
440                $cells = array();
441            foreach(explode("|", $row) as $cell) {
442                $ctyp = "d";
443                if (preg_match("/^_/", $cell)) $ctyp = "h";
444                if (preg_match("/^(_?$this->s$this->a$this->c\. )(.*)/", $cell, $cmtch)) {
445                    $catts = $this->pba($cmtch[1], 'td');
446                    $cell = $cmtch[2];
447                } else $catts = '';
448
449                $cell = $this->graf($this->span($cell));
450
451                if (trim($cell) != '')
452                    $cells[] = "\t\t\t<t$ctyp$catts>$cell</t$ctyp>";
453            }
454            $rows[] = "\t\t<tr$ratts>\n" . join("\n", $cells) . ($cells ? "\n" : "") . "\t\t</tr>";
455            unset($cells, $catts);
456        }
457        return "\t<table$tatts>\n" . join("\n", $rows) . "\n\t</table>\n\n";
458    }
459
460// -------------------------------------------------------------
461    function lists($text)
462    {
463        return preg_replace_callback("/^([#*]+$this->c .*)$(?![^#*])/smU", array(&$this, "fList"), $text);
464    }
465
466// -------------------------------------------------------------
467    function fList($m)
468    {
469        $text = explode("\n", $m[0]);
470        foreach($text as $line) {
471            $nextline = next($text);
472            if (preg_match("/^([#*]+)($this->a$this->c) (.*)$/s", $line, $m)) {
473                list(, $tl, $atts, $content) = $m;
474                $nl = '';
475                if (preg_match("/^([#*]+)\s.*/", $nextline, $nm))
476                	$nl = $nm[1];
477                if (!isset($lists[$tl])) {
478                    $lists[$tl] = true;
479                    $atts = $this->pba($atts);
480                    $line = "\t<" . $this->lT($tl) . "l$atts>\n\t\t<li>" . $this->graf($content);
481                } else {
482                    $line = "\t\t<li>" . $this->graf($content);
483                }
484
485                if(strlen($nl) <= strlen($tl)) $line .= "</li>";
486                foreach(array_reverse($lists) as $k => $v) {
487                    if(strlen($k) > strlen($nl)) {
488                        $line .= "\n\t</" . $this->lT($k) . "l>";
489                        if(strlen($k) > 1)
490                            $line .= "</li>";
491                        unset($lists[$k]);
492                    }
493                }
494            }
495            $out[] = $line;
496        }
497        return join("\n", $out);
498    }
499
500// -------------------------------------------------------------
501    function lT($in)
502    {
503        return preg_match("/^#+/", $in) ? 'o' : 'u';
504    }
505
506// -------------------------------------------------------------
507    function doPBr($in)
508    {
509        return preg_replace_callback('@<(p)([^>]*?)>(.*)(</\1>)@s', array(&$this, 'doBr'), $in);
510    }
511
512// -------------------------------------------------------------
513    function doBr($m)
514    {
515        $content = preg_replace("@(.+)(?<!<br>|<br />)\n(?![#*\s|])@", '$1<br />', $m[3]);
516        return '<'.$m[1].$m[2].'>'.$content.$m[4];
517    }
518
519// -------------------------------------------------------------
520    function block($text)
521    {
522        $find = $this->btag;
523        $tre = join('|', $find);
524
525        $text = explode("\n\n", $text);
526
527        $tag = 'p';
528        $atts = $cite = $graf = $ext  = '';
529
530        foreach($text as $line) {
531            $anon = 0;
532            if (preg_match("/^($tre)($this->a$this->c)\.(\.?)(?::(\S+))? (.*)$/s", $line, $m)) {
533                // last block was extended, so close it
534                if ($ext)
535                    $out[count($out)-1] .= $c1;
536                // new block
537                list(,$tag,$atts,$ext,$cite,$graf) = $m;
538                list($o1, $o2, $content, $c2, $c1) = $this->fBlock(array(0,$tag,$atts,$ext,$cite,$graf));
539
540                // leave off c1 if this block is extended, we'll close it at the start of the next block
541                if ($ext)
542                    $line = $o1.$o2.$content.$c2;
543                else
544                    $line = $o1.$o2.$content.$c2.$c1;
545            }
546            else {
547                // anonymous block
548                $anon = 1;
549                if ($ext or !preg_match('/^ /', $line)) {
550                    list($o1, $o2, $content, $c2, $c1) = $this->fBlock(array(0,$tag,$atts,$ext,$cite,$line));
551                    // skip $o1/$c1 because this is part of a continuing extended block
552                    if ($tag == 'p' and !$this->hasRawText($content)) {
553                        $line = $content;
554                    }
555                    else {
556                        $line = $o2.$content.$c2;
557                    }
558                }
559                else {
560                   $line = $this->graf($line);
561                }
562            }
563
564            $line = $this->doPBr($line);
565            $line = preg_replace('/<br>/', '<br />', $line);
566
567            if ($ext and $anon)
568                $out[count($out)-1] .= "\n".$line;
569            else
570                $out[] = $line;
571
572            if (!$ext) {
573                $tag = 'p';
574                $atts = '';
575                $cite = '';
576                $graf = '';
577            }
578        }
579        if ($ext) $out[count($out)-1] .= $c1;
580        return join("\n\n", $out);
581    }
582
583
584
585// -------------------------------------------------------------
586    function fBlock($m)
587    {
588        // $this->dump($m);
589        list(, $tag, $atts, $ext, $cite, $content) = $m;
590        $atts = $this->pba($atts);
591
592        $o1 = $o2 = $c2 = $c1 = '';
593
594        if (preg_match("/fn(\d+)/", $tag, $fns)) {
595            $tag = 'p';
596            $fnid = empty($this->fn[$fns[1]]) ? $fns[1] : $this->fn[$fns[1]];
597            $atts .= ' id="fn' . $fnid . '"';
598            if (strpos($atts, 'class=') === false)
599                $atts .= ' class="footnote"';
600            $content = '<sup>' . $fns[1] . '</sup> ' . $content;
601        }
602
603        if ($tag == "bq") {
604            $cite = $this->checkRefs($cite);
605            $cite = ($cite != '') ? ' cite="' . $cite . '"' : '';
606            $o1 = "\t<blockquote$cite$atts>\n";
607            $o2 = "\t\t<p$atts>";
608            $c2 = "</p>";
609            $c1 = "\n\t</blockquote>";
610        }
611        elseif ($tag == 'bc') {
612            $o1 = "<pre$atts>";
613            $o2 = "<code$atts>";
614            $c2 = "</code>";
615            $c1 = "</pre>";
616            $content = $this->shelve($this->encode_html(rtrim($content, "\n")."\n"));
617        }
618        elseif ($tag == 'notextile') {
619            $content = $this->shelve($content);
620            $o1 = $o2 = '';
621            $c1 = $c2 = '';
622        }
623        elseif ($tag == 'pre') {
624            $content = $this->shelve($this->encode_html(rtrim($content, "\n")."\n"));
625            $o1 = "<pre$atts>";
626            $o2 = $c2 = '';
627            $c1 = "</pre>";
628        }
629        else {
630            $o2 = "\t<$tag$atts>";
631            $c2 = "</$tag>";
632          }
633
634        $content = $this->graf($content);
635
636        return array($o1, $o2, $content, $c2, $c1);
637    }
638
639// -------------------------------------------------------------
640    function graf($text)
641    {
642        // handle normal paragraph text
643        if (!$this->lite) {
644            $text = $this->noTextile($text);
645            $text = $this->code($text);
646        }
647
648        $text = $this->links($text);
649        if (!$this->noimage)
650            $text = $this->image($text);
651
652        if (!$this->lite) {
653            $text = $this->lists($text);
654            $text = $this->table($text);
655        }
656
657        $text = $this->span($text);
658        $text = $this->footnoteRef($text);
659        $text = $this->glyphs($text);
660        return rtrim($text, "\n");
661    }
662
663// -------------------------------------------------------------
664    function span($text)
665    {
666        $qtags = array('\*\*','\*','\?\?','-','__','_','%','\+','~','\^');
667        $pnct = ".,\"'?!;:";
668
669        foreach($qtags as $f) {
670            $text = preg_replace_callback("/
671                (?:^|(?<=[\s>$pnct])|([{[]))
672                ($f)(?!$f)
673                ({$this->c})
674                (?::(\S+))?
675                ([^\s$f]+|\S[^$f\n]*[^\s$f\n])
676                ([$pnct]*)
677                $f
678                (?:$|([\]}])|(?=[[:punct:]]{1,2}|\s))
679            /x", array(&$this, "fSpan"), $text);
680        }
681        return $text;
682    }
683
684// -------------------------------------------------------------
685    function fSpan($m)
686    {
687        $qtags = array(
688            '*'  => 'strong',
689            '**' => 'b',
690            '??' => 'cite',
691            '_'  => 'em',
692            '__' => 'i',
693            '-'  => 'del',
694            '%'  => 'span',
695            '+'  => 'ins',
696            '~'  => 'sub',
697            '^'  => 'sup',
698        );
699
700        list(,, $tag, $atts, $cite, $content, $end) = $m;
701        $tag = $qtags[$tag];
702        $atts = $this->pba($atts);
703        $atts .= ($cite != '') ? 'cite="' . $cite . '"' : '';
704
705        $out = "<$tag$atts>$content$end</$tag>";
706
707//      $this->dump($out);
708
709        return $out;
710
711    }
712
713// -------------------------------------------------------------
714    function links($text)
715    {
716        return preg_replace_callback('/
717            (?:^|(?<=[\s>.$pnct\(])|([{[])) # $pre
718            "                            # start
719            (' . $this->c . ')           # $atts
720            ([^"]+)                      # $text
721            \s?
722            (?:\(([^)]+)\)(?="))?        # $title
723            ":
724            ('.$this->urlch.'+)          # $url
725            (\/)?                        # $slash
726            ([^\w\/;]*)                  # $post
727            (?:([\]}])|(?=\s|$|\)))
728        /Ux', array(&$this, "fLink"), $text);
729    }
730
731// -------------------------------------------------------------
732    function fLink($m)
733    {
734        list(, $pre, $atts, $text, $title, $url, $slash, $post) = $m;
735
736        $url = $this->checkRefs($url);
737
738        $atts = $this->pba($atts);
739        $atts .= ($title != '') ? ' title="' . $this->encode_html($title) . '"' : '';
740
741        if (!$this->noimage)
742            $text = $this->image($text);
743
744        $text = $this->span($text);
745        $text = $this->glyphs($text);
746
747        $url = $this->relURL($url);
748
749        $out = '<a href="' . $this->encode_html($url . $slash) . '"' . $atts . $this->rel . '>' . $text . '</a>' . $post;
750
751        // $this->dump($out);
752        return $this->shelve($out);
753
754    }
755
756// -------------------------------------------------------------
757    function getRefs($text)
758    {
759        return preg_replace_callback("/(?<=^|\s)\[(.+)\]((?:http:\/\/|\/)\S+)(?=\s|$)/U",
760            array(&$this, "refs"), $text);
761    }
762
763// -------------------------------------------------------------
764    function refs($m)
765    {
766        list(, $flag, $url) = $m;
767        $this->urlrefs[$flag] = $url;
768        return '';
769    }
770
771// -------------------------------------------------------------
772    function checkRefs($text)
773    {
774        return (isset($this->urlrefs[$text])) ? $this->urlrefs[$text] : $text;
775    }
776
777// -------------------------------------------------------------
778    function relURL($url)
779    {
780        $parts = parse_url($url);
781        if ((empty($parts['scheme']) or @$parts['scheme'] == 'http') and
782             empty($parts['host']) and
783             preg_match('/^\w/', @$parts['path']))
784            $url = $this->hu.$url;
785        if ($this->restricted and !empty($parts['scheme']) and
786              !in_array($parts['scheme'], $this->url_schemes))
787            return '#';
788        return $url;
789    }
790
791// -------------------------------------------------------------
792    function image($text)
793    {
794        return preg_replace_callback("/
795            (?:[[{])?          # pre
796            \!                 # opening !
797            (\<|\=|\>)??       # optional alignment atts
798            ($this->c)         # optional style,class atts
799            (?:\. )?           # optional dot-space
800            ([^\s(!]+)         # presume this is the src
801            \s?                # optional space
802            (?:\(([^\)]+)\))?  # optional title
803            \!                 # closing
804            (?::(\S+))?        # optional href
805            (?:[\]}]|(?=\s|$)) # lookahead: space or end of string
806        /Ux", array(&$this, "fImage"), $text);
807    }
808
809// -------------------------------------------------------------
810    function fImage($m)
811    {
812        list(, $algn, $atts, $url) = $m;
813        $atts  = $this->pba($atts);
814        $atts .= ($algn != '')  ? ' align="' . $this->iAlign($algn) . '"' : '';
815        $atts .= (isset($m[4])) ? ' title="' . $m[4] . '"' : '';
816        $atts .= (isset($m[4])) ? ' alt="'   . $m[4] . '"' : ' alt=""';
817        $size = @getimagesize($url);
818        if ($size) $atts .= " $size[3]";
819
820        $href = (isset($m[5])) ? $this->checkRefs($m[5]) : '';
821        $url = $this->checkRefs($url);
822
823        $url = $this->relURL($url);
824
825        $out = array(
826            ($href) ? '<a href="' . $href . '">' : '',
827            '<img src="' . $url . '"' . $atts . ' />',
828            ($href) ? '</a>' : ''
829        );
830
831        return join('',$out);
832    }
833
834// -------------------------------------------------------------
835    function code($text)
836    {
837        $text = $this->doSpecial($text, '<code>', '</code>', 'fCode');
838        $text = $this->doSpecial($text, '@', '@', 'fCode');
839        $text = $this->doSpecial($text, '<pre>', '</pre>', 'fPre');
840        return $text;
841    }
842
843// -------------------------------------------------------------
844    function fCode($m)
845    {
846      @list(, $before, $text, $after) = $m;
847      if ($this->restricted)
848          // $text is already escaped
849            return $before.$this->shelve('<code>'.$text.'</code>').$after;
850      else
851            return $before.$this->shelve('<code>'.$this->encode_html($text).'</code>').$after;
852    }
853
854// -------------------------------------------------------------
855    function fPre($m)
856    {
857      @list(, $before, $text, $after) = $m;
858      if ($this->restricted)
859          // $text is already escaped
860            return $before.'<pre>'.$this->shelve($text).'</pre>'.$after;
861      else
862            return $before.'<pre>'.$this->shelve($this->encode_html($text)).'</pre>'.$after;
863    }
864// -------------------------------------------------------------
865    function shelve($val)
866    {
867        $i = uniqid(rand());
868        $this->shelf[$i] = $val;
869        return $i;
870    }
871
872// -------------------------------------------------------------
873    function retrieve($text)
874    {
875        if (is_array($this->shelf))
876            do {
877                $old = $text;
878                $text = strtr($text, $this->shelf);
879             } while ($text != $old);
880
881        return $text;
882    }
883
884// -------------------------------------------------------------
885// NOTE: deprecated
886    function incomingEntities($text)
887    {
888        return preg_replace("/&(?![#a-z0-9]+;)/i", "x%x%", $text);
889    }
890
891// -------------------------------------------------------------
892// NOTE: deprecated
893    function encodeEntities($text)
894    {
895        return (function_exists('mb_encode_numericentity'))
896        ?    $this->encode_high($text)
897        :    htmlentities($text, ENT_NOQUOTES, "utf-8");
898    }
899
900// -------------------------------------------------------------
901// NOTE: deprecated
902    function fixEntities($text)
903    {
904        /*  de-entify any remaining angle brackets or ampersands */
905        return str_replace(array("&gt;", "&lt;", "&amp;"),
906            array(">", "<", "&"), $text);
907    }
908
909// -------------------------------------------------------------
910    function cleanWhiteSpace($text)
911    {
912        $out = str_replace("\r\n", "\n", $text);
913        $out = preg_replace("/\n{3,}/", "\n\n", $out);
914        $out = preg_replace("/\n *\n/", "\n\n", $out);
915        $out = preg_replace('/"$/', "\" ", $out);
916        return $out;
917    }
918
919// -------------------------------------------------------------
920    function doSpecial($text, $start, $end, $method='fSpecial')
921    {
922      return preg_replace_callback('/(^|\s|[[({>])'.preg_quote($start, '/').'(.*?)'.preg_quote($end, '/').'(\s|$|[\])}])?/ms',
923            array(&$this, $method), $text);
924    }
925
926// -------------------------------------------------------------
927    function fSpecial($m)
928    {
929        // A special block like notextile or code
930      @list(, $before, $text, $after) = $m;
931        return $before.$this->shelve($this->encode_html($text)).$after;
932    }
933
934// -------------------------------------------------------------
935    function noTextile($text)
936    {
937         $text = $this->doSpecial($text, '<notextile>', '</notextile>', 'fTextile');
938         return $this->doSpecial($text, '==', '==', 'fTextile');
939
940    }
941
942// -------------------------------------------------------------
943    function fTextile($m)
944    {
945        @list(, $before, $notextile, $after) = $m;
946        #$notextile = str_replace(array_keys($modifiers), array_values($modifiers), $notextile);
947        return $before.$this->shelve($notextile).$after;
948    }
949
950// -------------------------------------------------------------
951    function footnoteRef($text)
952    {
953        return preg_replace('/\b\[([0-9]+)\](\s)?/Ue',
954            '$this->footnoteID(\'\1\',\'\2\')', $text);
955    }
956
957// -------------------------------------------------------------
958    function footnoteID($id, $t)
959    {
960        if (empty($this->fn[$id]))
961            $this->fn[$id] = uniqid(rand());
962        $fnid = $this->fn[$id];
963        return '<sup class="footnote"><a href="#fn'.$fnid.'">'.$id.'</a></sup>'.$t;
964    }
965
966// -------------------------------------------------------------
967    function glyphs($text)
968    {
969        // fix: hackish
970        $text = preg_replace('/"\z/', "\" ", $text);
971        $pnc = '[[:punct:]]';
972
973        $glyph_search = array(
974            '/(\w)\'(\w)/',                                      // apostrophe's
975            '/(\s)\'(\d+\w?)\b(?!\')/',                          // back in '88
976            '/(\S)\'(?=\s|'.$pnc.'|<|$)/',                       //  single closing
977            '/\'/',                                              //  single opening
978            '/(\S)\"(?=\s|'.$pnc.'|<|$)/',                       //  double closing
979            '/"/',                                               //  double opening
980            '/\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/',        //  3+ uppercase acronym
981            '/\b([A-Z][A-Z\'\-]+[A-Z])(?=[\s.,\)>])/',           //  3+ uppercase
982            '/\b( )?\.{3}/',                                     //  ellipsis
983            '/(\s?)--(\s?)/',                                    //  em dash
984            '/\s-(?:\s|$)/',                                     //  en dash
985            '/(\d+)( ?)x( ?)(?=\d+)/',                           //  dimension sign
986            '/\b ?[([]TM[])]/i',                                 //  trademark
987            '/\b ?[([]R[])]/i',                                  //  registered
988            '/\b ?[([]C[])]/i',                                  //  copyright
989         );
990
991        extract($this->glyph, EXTR_PREFIX_ALL, 'txt');
992
993        $glyph_replace = array(
994            '$1'.$txt_apostrophe.'$2',           // apostrophe's
995            '$1'.$txt_apostrophe.'$2',           // back in '88
996            '$1'.$txt_quote_single_close,        //  single closing
997            $txt_quote_single_open,              //  single opening
998            '$1'.$txt_quote_double_close,        //  double closing
999            $txt_quote_double_open,              //  double opening
1000            '<acronym title="$2">$1</acronym>',  //  3+ uppercase acronym
1001            '<span class="caps">$1</span>',      //  3+ uppercase
1002            '$1'.$txt_ellipsis,                  //  ellipsis
1003            '$1'.$txt_emdash.'$2',               //  em dash
1004            ' '.$txt_endash.' ',                 //  en dash
1005            '$1$2'.$txt_dimension.'$3',          //  dimension sign
1006            $txt_trademark,                      //  trademark
1007            $txt_registered,                     //  registered
1008            $txt_copyright,                      //  copyright
1009         );
1010
1011         $text = preg_split("/(<.*>)/U", $text, -1, PREG_SPLIT_DELIM_CAPTURE);
1012         foreach($text as $line) {
1013             if (!preg_match("/<.*>/", $line)) {
1014                 $line = preg_replace($glyph_search, $glyph_replace, $line);
1015             }
1016              $glyph_out[] = $line;
1017         }
1018         return join('', $glyph_out);
1019    }
1020
1021// -------------------------------------------------------------
1022    function iAlign($in)
1023    {
1024        $vals = array(
1025            '<' => 'left',
1026            '=' => 'center',
1027            '>' => 'right');
1028        return (isset($vals[$in])) ? $vals[$in] : '';
1029    }
1030
1031// -------------------------------------------------------------
1032    function hAlign($in)
1033    {
1034        $vals = array(
1035            '<'  => 'left',
1036            '='  => 'center',
1037            '>'  => 'right',
1038            '<>' => 'justify');
1039        return (isset($vals[$in])) ? $vals[$in] : '';
1040    }
1041
1042// -------------------------------------------------------------
1043    function vAlign($in)
1044    {
1045        $vals = array(
1046            '^' => 'top',
1047            '-' => 'middle',
1048            '~' => 'bottom');
1049        return (isset($vals[$in])) ? $vals[$in] : '';
1050    }
1051
1052// -------------------------------------------------------------
1053// NOTE: deprecated
1054    function encode_high($text, $charset = "UTF-8")
1055    {
1056        return mb_encode_numericentity($text, $this->cmap(), $charset);
1057    }
1058
1059// -------------------------------------------------------------
1060// NOTE: deprecated
1061    function decode_high($text, $charset = "UTF-8")
1062    {
1063        return mb_decode_numericentity($text, $this->cmap(), $charset);
1064    }
1065
1066// -------------------------------------------------------------
1067// NOTE: deprecated
1068    function cmap()
1069    {
1070        $f = 0xffff;
1071        $cmap = array(
1072            0x0080, 0xffff, 0, $f);
1073        return $cmap;
1074    }
1075
1076// -------------------------------------------------------------
1077    function encode_html($str, $quotes=1)
1078    {
1079        $a = array(
1080            '&' => '&#38;',
1081            '<' => '&#60;',
1082            '>' => '&#62;',
1083        );
1084        if ($quotes) $a = $a + array(
1085            "'" => '&#39;',
1086            '"' => '&#34;',
1087        );
1088
1089        return strtr($str, $a);
1090    }
1091
1092// -------------------------------------------------------------
1093    function textile_popup_help($name, $helpvar, $windowW, $windowH)
1094    {
1095        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 />';
1096
1097        return $out;
1098    }
1099
1100// -------------------------------------------------------------
1101// NOTE: deprecated
1102    function txtgps($thing)
1103    {
1104        if (isset($_POST[$thing])) {
1105            if (get_magic_quotes_gpc()) {
1106                return stripslashes($_POST[$thing]);
1107            }
1108            else {
1109                return $_POST[$thing];
1110            }
1111        }
1112        else {
1113            return '';
1114        }
1115    }
1116
1117// -------------------------------------------------------------
1118// NOTE: deprecated
1119    function dump()
1120    {
1121        foreach (func_get_args() as $a)
1122            echo "\n<pre>",(is_array($a)) ? print_r($a) : $a, "</pre>\n";
1123    }
1124
1125// -------------------------------------------------------------
1126
1127    function blockLite($text)
1128    {
1129        $this->btag = array('bq', 'p');
1130        return $this->block($text."\n\n");
1131    }
1132
1133
1134} // end class
1135
1136?>
1137