Project

General

Profile

Statistics
| Branch: | Revision:

colonymech / docs / www / colonyscout / internal / jeditable / php / Textile.php @ f59acf11

History | View | Annotate | Download (37.9 KB)

1 f59acf11 Dan Shope
<?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: Textile.php,v 1.2 2006/12/11 10:18:59 toni Exp $
13
$LastChangedRevision: 1998 $
14
*/
15
16
/*
17

18
_____________
19
T E X T I L E
20

21
A Humane Web Text Generator
22

23
Version 2.0
24

25
Copyright (c) 2003-2004, Dean Allen <dean@textism.com>
26
All rights reserved.
27

28
Thanks to Carlo Zottmann <carlo@g-blog.net> for refactoring
29
Textile's procedural code into a class framework
30

31
Additions and fixes Copyright (c) 2006 Alex Shiels http://thresholdstate.com/
32

33
_____________
34
L I C E N S E
35

36
Redistribution and use in source and binary forms, with or without
37
modification, 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

50
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
51
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
52
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
53
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
54
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
55
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
56
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
57
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
58
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
59
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
60
POSSIBILITY OF SUCH DAMAGE.
61

62
_________
63
U S A G E
64

65
Block 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

92
Phrase 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

115
ABC(Always Be Closing)  ->   <acronym title="Always Be Closing">ABC</acronym>
116

117

118
Table 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

134
Applying 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
206
class 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: 1998 $';
232
    
233
    var $doc_root;
234
235
// -------------------------------------------------------------
236
    function Textile()
237
    {
238
        $this->hlgn = "(?:\<(?!>)|(?<!<)\>|\<\>|\=|[()]+(?! ))";
239
        $this->vlgn = "[\-^~]";
240
        $this->clas = "(?:\([^)]+\))";
241
        $this->lnge = "(?:\[[^]]+\])";
242
        $this->styl = "(?:\{[^}]+\})";
243
        $this->cspn = "(?:\\\\\d+)";
244
        $this->rspn = "(?:\/\d+)";
245
        $this->a = "(?:{$this->hlgn}|{$this->vlgn})*";
246
        $this->s = "(?:{$this->cspn}|{$this->rspn})*";
247
        $this->c = "(?:{$this->clas}|{$this->styl}|{$this->lnge}|{$this->hlgn})*";
248
249
        $this->pnct = '[\!"#\$%&\'()\*\+,\-\./:;<=>\?@\[\\\]\^_`{\|}\~]';
250
        $this->urlch = '[\w"$\-_.+!*\'(),";\/?:@=&%#{}|\\^~\[\]`]';
251
252
        $this->url_schemes = array('http','https','ftp','mailto');
253
254
        $this->btag = array('bq', 'bc', 'notextile', 'pre', 'h[1-6]', 'fn\d+', 'p');
255
256
        $this->glyph = array(
257
           'quote_single_open'  => txt_quote_single_open,
258
           'quote_single_close' => txt_quote_single_close,
259
           'quote_double_open'  => txt_quote_double_open,
260
           'quote_double_close' => txt_quote_double_close,
261
           'apostrophe'         => txt_apostrophe,
262
           'prime'              => txt_prime,
263
           'prime_double'       => txt_prime_double,
264
           'ellipsis'           => txt_ellipsis,
265
           'emdash'             => txt_emdash,
266
           'endash'             => txt_endash,
267
           'dimension'          => txt_dimension,
268
           'trademark'          => txt_trademark,
269
           'registered'         => txt_registered,
270
           'copyright'          => txt_copyright,
271
        );
272
273
        if (defined('hu'))
274
            $this->hu = hu;
275
276
        if (defined('DIRECTORY_SEPARATOR'))
277
            $this->ds = constant('DIRECTORY_SEPARATOR');
278
        else
279
            $this->ds = '/';
280
281
        $this->doc_root = @$_SERVER['DOCUMENT_ROOT'];
282
        if (!$this->doc_root)
283
            $this->doc_root = @$_SERVER['PATH_TRANSLATED']; // IIS
284
            
285
        $this->doc_root = rtrim($this->doc_root, $this->ds).$this->ds;
286
287
    }
288
289
// -------------------------------------------------------------
290
291
        function TextileThis($text, $lite = '', $encode = '', $noimage = '', $strict = '', $rel = '')
292
        {
293
                $this->rel = ($rel) ? ' rel="'.$rel.'"' : '';
294
295
                $this->lite = $lite;
296
                $this->noimage = $noimage;
297
298
        if ($encode) {
299
         $text = $this->incomingEntities($text);
300
            $text = str_replace("x%x%", "&#38;", $text);
301
            return $text;
302
        } else {
303
304
            if(!$strict) {
305
                $text = $this->cleanWhiteSpace($text);
306
            }
307
308
            $text = $this->getRefs($text);
309
310
            if (!$lite) {
311
                $text = $this->block($text);
312
            }
313
314
            $text = $this->retrieve($text);
315
316
                // just to be tidy
317
            $text = str_replace("<br />", "<br />\n", $text);
318
319
            return $text;
320
        }
321
    }
322
323
// -------------------------------------------------------------
324
325
        function TextileRestricted($text, $lite = 1, $noimage = 1, $rel = 'nofollow')
326
        {
327
                $this->restricted = true;
328
                $this->lite = $lite;
329
                $this->noimage = $noimage;
330
331
                $this->rel = ($rel) ? ' rel="'.$rel.'"' : '';
332
333
            // escape any raw html
334
            $text = $this->encode_html($text, 0);
335
336
            $text = $this->cleanWhiteSpace($text);
337
            $text = $this->getRefs($text);
338
339
            if ($lite) {
340
                $text = $this->blockLite($text);
341
            }
342
            else {
343
                $text = $this->block($text);
344
            }
345
346
            $text = $this->retrieve($text);
347
348
                // just to be tidy
349
            $text = str_replace("<br />", "<br />\n", $text);
350
351
            return $text;
352
    }
353
354
// -------------------------------------------------------------
355
    function pba($in, $element = "") // "parse block attributes"
356
    {
357
        $style = '';
358
        $class = '';
359
        $lang = '';
360
        $colspan = '';
361
        $rowspan = '';
362
        $id = '';
363
        $atts = '';
364
365
        if (!empty($in)) {
366
            $matched = $in;
367
            if ($element == 'td') {
368
                if (preg_match("/\\\\(\d+)/", $matched, $csp)) $colspan = $csp[1];
369
                if (preg_match("/\/(\d+)/", $matched, $rsp)) $rowspan = $rsp[1];
370
            }
371
372
            if ($element == 'td' or $element == 'tr') {
373
                if (preg_match("/($this->vlgn)/", $matched, $vert))
374
                    $style[] = "vertical-align:" . $this->vAlign($vert[1]) . ";";
375
            }
376
377
            if (preg_match("/\{([^}]*)\}/", $matched, $sty)) {
378
                $style[] = rtrim($sty[1], ';') . ';';
379
                $matched = str_replace($sty[0], '', $matched);
380
            }
381
382
            if (preg_match("/\[([^]]+)\]/U", $matched, $lng)) {
383
                $lang = $lng[1];
384
                $matched = str_replace($lng[0], '', $matched);
385
            }
386
387
            if (preg_match("/\(([^()]+)\)/U", $matched, $cls)) {
388
                $class = $cls[1];
389
                $matched = str_replace($cls[0], '', $matched);
390
            }
391
392
            if (preg_match("/([(]+)/", $matched, $pl)) {
393
                $style[] = "padding-left:" . strlen($pl[1]) . "em;";
394
                $matched = str_replace($pl[0], '', $matched);
395
            }
396
397
            if (preg_match("/([)]+)/", $matched, $pr)) {
398
                // $this->dump($pr);
399
                $style[] = "padding-right:" . strlen($pr[1]) . "em;";
400
                $matched = str_replace($pr[0], '', $matched);
401
            }
402
403
            if (preg_match("/($this->hlgn)/", $matched, $horiz))
404
                $style[] = "text-align:" . $this->hAlign($horiz[1]) . ";";
405
406
            if (preg_match("/^(.*)#(.*)$/", $class, $ids)) {
407
                $id = $ids[2];
408
                $class = $ids[1];
409
            }
410
411
            if ($this->restricted)
412
                return ($lang)    ? ' lang="'    . $lang            .'"':'';
413
414
            return join('',array(
415
                ($style)   ? ' style="'   . join("", $style) .'"':'',
416
                ($class)   ? ' class="'   . $class           .'"':'',
417
                ($lang)    ? ' lang="'    . $lang            .'"':'',
418
                ($id)      ? ' id="'      . $id              .'"':'',
419
                ($colspan) ? ' colspan="' . $colspan         .'"':'',
420
                ($rowspan) ? ' rowspan="' . $rowspan         .'"':''
421
            ));
422
        }
423
        return '';
424
    }
425
426
// -------------------------------------------------------------
427
    function hasRawText($text)
428
    {
429
        // checks whether the text has text not already enclosed by a block tag
430
        $r = trim(preg_replace('@<(p|blockquote|div|form|table|ul|ol|pre|h\d)[^>]*?>.*</\1>@s', '', trim($text)));
431
        $r = trim(preg_replace('@<(hr|br)[^>]*?/>@', '', $r));
432
        return '' != $r;
433
    }
434
435
// -------------------------------------------------------------
436
    function table($text)
437
    {
438
        $text = $text . "\n\n";
439
        return preg_replace_callback("/^(?:table(_?{$this->s}{$this->a}{$this->c})\. ?\n)?^({$this->a}{$this->c}\.? ?\|.*\|)\n\n/smU",
440
           array(&$this, "fTable"), $text);
441
    }
442
443
// -------------------------------------------------------------
444
    function fTable($matches)
445
    {
446
        $tatts = $this->pba($matches[1], 'table');
447
448
        foreach(preg_split("/\|$/m", $matches[2], -1, PREG_SPLIT_NO_EMPTY) as $row) {
449
            if (preg_match("/^($this->a$this->c\. )(.*)/m", ltrim($row), $rmtch)) {
450
                $ratts = $this->pba($rmtch[1], 'tr');
451
                $row = $rmtch[2];
452
            } else $ratts = '';
453
454
                $cells = array();
455
            foreach(explode("|", $row) as $cell) {
456
                $ctyp = "d";
457
                if (preg_match("/^_/", $cell)) $ctyp = "h";
458
                if (preg_match("/^(_?$this->s$this->a$this->c\. )(.*)/", $cell, $cmtch)) {
459
                    $catts = $this->pba($cmtch[1], 'td');
460
                    $cell = $cmtch[2];
461
                } else $catts = '';
462
463
                $cell = $this->graf($this->span($cell));
464
465
                if (trim($cell) != '')
466
                    $cells[] = "\t\t\t<t$ctyp$catts>$cell</t$ctyp>";
467
            }
468
            $rows[] = "\t\t<tr$ratts>\n" . join("\n", $cells) . ($cells ? "\n" : "") . "\t\t</tr>";
469
            unset($cells, $catts);
470
        }
471
        return "\t<table$tatts>\n" . join("\n", $rows) . "\n\t</table>\n\n";
472
    }
473
474
// -------------------------------------------------------------
475
    function lists($text)
476
    {
477
        return preg_replace_callback("/^([#*]+$this->c .*)$(?![^#*])/smU", array(&$this, "fList"), $text);
478
    }
479
480
// -------------------------------------------------------------
481
    function fList($m)
482
    {
483
        $text = explode("\n", $m[0]);
484
        foreach($text as $line) {
485
            $nextline = next($text);
486
            if (preg_match("/^([#*]+)($this->a$this->c) (.*)$/s", $line, $m)) {
487
                list(, $tl, $atts, $content) = $m;
488
                $nl = '';
489
                if (preg_match("/^([#*]+)\s.*/", $nextline, $nm))
490
                        $nl = $nm[1];
491
                if (!isset($lists[$tl])) {
492
                    $lists[$tl] = true;
493
                    $atts = $this->pba($atts);
494
                    $line = "\t<" . $this->lT($tl) . "l$atts>\n\t\t<li>" . $this->graf($content);
495
                } else {
496
                    $line = "\t\t<li>" . $this->graf($content);
497
                }
498
499
                if(strlen($nl) <= strlen($tl)) $line .= "</li>";
500
                foreach(array_reverse($lists) as $k => $v) {
501
                    if(strlen($k) > strlen($nl)) {
502
                        $line .= "\n\t</" . $this->lT($k) . "l>";
503
                        if(strlen($k) > 1)
504
                            $line .= "</li>";
505
                        unset($lists[$k]);
506
                    }
507
                }
508
            }
509
            $out[] = $line;
510
        }
511
        return join("\n", $out);
512
    }
513
514
// -------------------------------------------------------------
515
    function lT($in)
516
    {
517
        return preg_match("/^#+/", $in) ? 'o' : 'u';
518
    }
519
520
// -------------------------------------------------------------
521
    function doPBr($in)
522
    {
523
        return preg_replace_callback('@<(p)([^>]*?)>(.*)(</\1>)@s', array(&$this, 'doBr'), $in);
524
    }
525
526
// -------------------------------------------------------------
527
    function doBr($m)
528
    {
529
        $content = preg_replace("@(.+)(?<!<br>|<br />)\n(?![#*\s|])@", '$1<br />', $m[3]);
530
        return '<'.$m[1].$m[2].'>'.$content.$m[4];
531
    }
532
533
// -------------------------------------------------------------
534
    function block($text)
535
    {
536
        $find = $this->btag;
537
        $tre = join('|', $find);
538
539
        $text = explode("\n\n", $text);
540
541
        $tag = 'p';
542
        $atts = $cite = $graf = $ext  = '';
543
544
        foreach($text as $line) {
545
            $anon = 0;
546
            if (preg_match("/^($tre)($this->a$this->c)\.(\.?)(?::(\S+))? (.*)$/s", $line, $m)) {
547
                // last block was extended, so close it
548
                if ($ext)
549
                    $out[count($out)-1] .= $c1;
550
                // new block
551
                list(,$tag,$atts,$ext,$cite,$graf) = $m;
552
                list($o1, $o2, $content, $c2, $c1) = $this->fBlock(array(0,$tag,$atts,$ext,$cite,$graf));
553
554
                // leave off c1 if this block is extended, we'll close it at the start of the next block
555
                if ($ext)
556
                    $line = $o1.$o2.$content.$c2;
557
                else
558
                    $line = $o1.$o2.$content.$c2.$c1;
559
            }
560
            else {
561
                // anonymous block
562
                $anon = 1;
563
                if ($ext or !preg_match('/^ /', $line)) {
564
                    list($o1, $o2, $content, $c2, $c1) = $this->fBlock(array(0,$tag,$atts,$ext,$cite,$line));
565
                    // skip $o1/$c1 because this is part of a continuing extended block
566
                    if ($tag == 'p' and !$this->hasRawText($content)) {
567
                        $line = $content;
568
                    }
569
                    else {
570
                        $line = $o2.$content.$c2;
571
                    }
572
                }
573
                else {
574
                   $line = $this->graf($line);
575
                }
576
            }
577
578
            $line = $this->doPBr($line);
579
            $line = preg_replace('/<br>/', '<br />', $line);
580
581
            if ($ext and $anon)
582
                $out[count($out)-1] .= "\n".$line;
583
            else
584
                $out[] = $line;
585
586
            if (!$ext) {
587
                $tag = 'p';
588
                $atts = '';
589
                $cite = '';
590
                $graf = '';
591
            }
592
        }
593
        if ($ext) $out[count($out)-1] .= $c1;
594
        return join("\n\n", $out);
595
    }
596
597
598
599
// -------------------------------------------------------------
600
    function fBlock($m)
601
    {
602
        // $this->dump($m);
603
        list(, $tag, $atts, $ext, $cite, $content) = $m;
604
        $atts = $this->pba($atts);
605
606
        $o1 = $o2 = $c2 = $c1 = '';
607
608
        if (preg_match("/fn(\d+)/", $tag, $fns)) {
609
            $tag = 'p';
610
            $fnid = empty($this->fn[$fns[1]]) ? $fns[1] : $this->fn[$fns[1]];
611
            $atts .= ' id="fn' . $fnid . '"';
612
            if (strpos($atts, 'class=') === false)
613
                $atts .= ' class="footnote"';
614
            $content = '<sup>' . $fns[1] . '</sup> ' . $content;
615
        }
616
617
        if ($tag == "bq") {
618
            $cite = $this->checkRefs($cite);
619
            $cite = ($cite != '') ? ' cite="' . $cite . '"' : '';
620
            $o1 = "\t<blockquote$cite$atts>\n";
621
            $o2 = "\t\t<p$atts>";
622
            $c2 = "</p>";
623
            $c1 = "\n\t</blockquote>";
624
        }
625
        elseif ($tag == 'bc') {
626
            $o1 = "<pre$atts>";
627
            $o2 = "<code$atts>";
628
            $c2 = "</code>";
629
            $c1 = "</pre>";
630
            $content = $this->shelve($this->r_encode_html(rtrim($content, "\n")."\n"));
631
        }
632
        elseif ($tag == 'notextile') {
633
            $content = $this->shelve($content);
634
            $o1 = $o2 = '';
635
            $c1 = $c2 = '';
636
        }
637
        elseif ($tag == 'pre') {
638
            $content = $this->shelve($this->r_encode_html(rtrim($content, "\n")."\n"));
639
            $o1 = "<pre$atts>";
640
            $o2 = $c2 = '';
641
            $c1 = "</pre>";
642
        }
643
        else {
644
            $o2 = "\t<$tag$atts>";
645
            $c2 = "</$tag>";
646
          }
647
648
        $content = $this->graf($content);
649
650
        return array($o1, $o2, $content, $c2, $c1);
651
    }
652
653
// -------------------------------------------------------------
654
    function graf($text)
655
    {
656
        // handle normal paragraph text
657
        if (!$this->lite) {
658
            $text = $this->noTextile($text);
659
            $text = $this->code($text);
660
        }
661
662
        $text = $this->links($text);
663
        if (!$this->noimage)
664
            $text = $this->image($text);
665
666
        if (!$this->lite) {
667
            $text = $this->lists($text);
668
            $text = $this->table($text);
669
        }
670
671
        $text = $this->span($text);
672
        $text = $this->footnoteRef($text);
673
        $text = $this->glyphs($text);
674
        return rtrim($text, "\n");
675
    }
676
677
// -------------------------------------------------------------
678
    function span($text)
679
    {
680
        $qtags = array('\*\*','\*','\?\?','-','__','_','%','\+','~','\^');
681
        $pnct = ".,\"'?!;:";
682
683
        foreach($qtags as $f) {
684
            $text = preg_replace_callback("/
685
                (?:^|(?<=[\s>$pnct\(])|([{[]))
686
                ($f)(?!$f)
687
                ({$this->c})
688
                (?::(\S+))?
689
                ([^\s$f]+|\S.*?[^\s$f\n])
690
                ([$pnct]*)
691
                $f
692
                (?:$|([\]}])|(?=[[:punct:]]{1,2}|\s|\)))
693
            /x", array(&$this, "fSpan"), $text);
694
        }
695
        return $text;
696
    }
697
698
// -------------------------------------------------------------
699
    function fSpan($m)
700
    {
701
        $qtags = array(
702
            '*'  => 'strong',
703
            '**' => 'b',
704
            '??' => 'cite',
705
            '_'  => 'em',
706
            '__' => 'i',
707
            '-'  => 'del',
708
            '%'  => 'span',
709
            '+'  => 'ins',
710
            '~'  => 'sub',
711
            '^'  => 'sup',
712
        );
713
714
        list(,, $tag, $atts, $cite, $content, $end) = $m;
715
        $tag = $qtags[$tag];
716
        $atts = $this->pba($atts);
717
        $atts .= ($cite != '') ? 'cite="' . $cite . '"' : '';
718
719
        $out = "<$tag$atts>$content$end</$tag>";
720
721
//      $this->dump($out);
722
723
        return $out;
724
725
    }
726
727
// -------------------------------------------------------------
728
    function links($text)
729
    {
730
        return preg_replace_callback('/
731
            (?:^|(?<=[\s>.$pnct\(])|([{[])) # $pre
732
            "                            # start
733
            (' . $this->c . ')           # $atts
734
            ([^"]+)                      # $text
735
            (?:\(([^)]+)\)(?="))?        # $title
736
            ":
737
            ('.$this->urlch.'+)          # $url
738
            (\/)?                        # $slash
739
            ([^\w\/;]*)                  # $post
740
            (?:([\]}])|(?=\s|$|\)))
741
        /Ux', array(&$this, "fLink"), $text);
742
    }
743
744
// -------------------------------------------------------------
745
    function fLink($m)
746
    {
747
        list(, $pre, $atts, $text, $title, $url, $slash, $post) = $m;
748
749
        $url = $this->checkRefs($url);
750
751
        $atts = $this->pba($atts);
752
        $atts .= ($title != '') ? ' title="' . $this->encode_html($title) . '"' : '';
753
754
        if (!$this->noimage)
755
            $text = $this->image($text);
756
757
        $text = $this->span($text);
758
        $text = $this->glyphs($text);
759
760
        $url = $this->relURL($url);
761
762
        $out = '<a href="' . $this->r_encode_html($url . $slash) . '"' . $atts . $this->rel . '>' . trim($text) . '</a>' . $post;
763
764
        // $this->dump($out);
765
        return $this->shelve($out);
766
767
    }
768
769
// -------------------------------------------------------------
770
    function getRefs($text)
771
    {
772
        return preg_replace_callback("/(?<=^|\s)\[(.+)\]((?:http:\/\/|\/)\S+)(?=\s|$)/U",
773
            array(&$this, "refs"), $text);
774
    }
775
776
// -------------------------------------------------------------
777
    function refs($m)
778
    {
779
        list(, $flag, $url) = $m;
780
        $this->urlrefs[$flag] = $url;
781
        return '';
782
    }
783
784
// -------------------------------------------------------------
785
    function checkRefs($text)
786
    {
787
        return (isset($this->urlrefs[$text])) ? $this->urlrefs[$text] : $text;
788
    }
789
790
// -------------------------------------------------------------
791
    function relURL($url)
792
    {
793
        $parts = parse_url($url);
794
        if ((empty($parts['scheme']) or @$parts['scheme'] == 'http') and
795
             empty($parts['host']) and
796
             preg_match('/^\w/', @$parts['path']))
797
            $url = $this->hu.$url;
798
        if ($this->restricted and !empty($parts['scheme']) and
799
              !in_array($parts['scheme'], $this->url_schemes))
800
            return '#';
801
        return $url;
802
    }
803
804
// -------------------------------------------------------------
805
    function isRelURL($url)
806
    {
807
        $parts = parse_url($url);
808
        return (empty($parts['scheme']) and empty($parts['host']));
809
    }
810
811
// -------------------------------------------------------------
812
    function image($text)
813
    {
814
        return preg_replace_callback("/
815
            (?:[[{])?          # pre
816
            \!                 # opening !
817
            (\<|\=|\>)?        # optional alignment atts
818
            ($this->c)         # optional style,class atts
819
            (?:\. )?           # optional dot-space
820
            ([^\s(!]+)         # presume this is the src
821
            \s?                # optional space
822
            (?:\(([^\)]+)\))?  # optional title
823
            \!                 # closing
824
            (?::(\S+))?        # optional href
825
            (?:[\]}]|(?=\s|$|\))) # lookahead: space or end of string
826
        /x", array(&$this, "fImage"), $text);
827
    }
828
829
// -------------------------------------------------------------
830
    function fImage($m)
831
    {
832
        list(, $algn, $atts, $url) = $m;
833
        $atts  = $this->pba($atts);
834
        $atts .= ($algn != '')  ? ' align="' . $this->iAlign($algn) . '"' : '';
835
        $atts .= (isset($m[4])) ? ' title="' . $m[4] . '"' : '';
836
        $atts .= (isset($m[4])) ? ' alt="'   . $m[4] . '"' : ' alt=""';
837
        $size = false;
838
        if ($this->isRelUrl($url))
839
                $size = @getimagesize(realpath($this->doc_root.ltrim($url, $this->ds)));
840
        if ($size) $atts .= " $size[3]";
841
842
        $href = (isset($m[5])) ? $this->checkRefs($m[5]) : '';
843
        $url = $this->checkRefs($url);
844
845
        $url = $this->relURL($url);
846
847
        $out = array(
848
            ($href) ? '<a href="' . $href . '">' : '',
849
            '<img src="' . $url . '"' . $atts . ' />',
850
            ($href) ? '</a>' : ''
851
        );
852
853
        return join('',$out);
854
    }
855
856
// -------------------------------------------------------------
857
    function code($text)
858
    {
859
        $text = $this->doSpecial($text, '<code>', '</code>', 'fCode');
860
        $text = $this->doSpecial($text, '@', '@', 'fCode');
861
        $text = $this->doSpecial($text, '<pre>', '</pre>', 'fPre');
862
        return $text;
863
    }
864
865
// -------------------------------------------------------------
866
    function fCode($m)
867
    {
868
      @list(, $before, $text, $after) = $m;
869
      return $before.$this->shelve('<code>'.$this->r_encode_html($text).'</code>').$after;
870
    }
871
872
// -------------------------------------------------------------
873
    function fPre($m)
874
    {
875
      @list(, $before, $text, $after) = $m;
876
      return $before.'<pre>'.$this->shelve($this->r_encode_html($text)).'</pre>'.$after;
877
    }
878
// -------------------------------------------------------------
879
    function shelve($val)
880
    {
881
        $i = uniqid(rand());
882
        $this->shelf[$i] = $val;
883
        return $i;
884
    }
885
886
// -------------------------------------------------------------
887
    function retrieve($text)
888
    {
889
        if (is_array($this->shelf))
890
            do {
891
                $old = $text;
892
                $text = strtr($text, $this->shelf);
893
             } while ($text != $old);
894
895
        return $text;
896
    }
897
898
// -------------------------------------------------------------
899
// NOTE: deprecated
900
    function incomingEntities($text)
901
    {
902
        return preg_replace("/&(?![#a-z0-9]+;)/i", "x%x%", $text);
903
    }
904
905
// -------------------------------------------------------------
906
// NOTE: deprecated
907
    function encodeEntities($text)
908
    {
909
        return (function_exists('mb_encode_numericentity'))
910
        ?    $this->encode_high($text)
911
        :    htmlentities($text, ENT_NOQUOTES, "utf-8");
912
    }
913
914
// -------------------------------------------------------------
915
// NOTE: deprecated
916
    function fixEntities($text)
917
    {
918
        /*  de-entify any remaining angle brackets or ampersands */
919
        return str_replace(array("&gt;", "&lt;", "&amp;"),
920
            array(">", "<", "&"), $text);
921
    }
922
923
// -------------------------------------------------------------
924
    function cleanWhiteSpace($text)
925
    {
926
        $out = str_replace("\r\n", "\n", $text);        # DOS line endings
927
        $out = preg_replace("/^ *\n/m", "\n", $out);    # lines containing only spaces
928
        $out = preg_replace("/\n{3,}/", "\n\n", $out);  # 3 or more line ends
929
        $out = preg_replace("/^\n*/", "", $out);        # leading blank lines
930
        return $out;
931
    }
932
933
// -------------------------------------------------------------
934
    function doSpecial($text, $start, $end, $method='fSpecial')
935
    {
936
      return preg_replace_callback('/(^|\s|[[({>])'.preg_quote($start, '/').'(.*?)'.preg_quote($end, '/').'(\s|$|[\])}])?/ms',
937
            array(&$this, $method), $text);
938
    }
939
940
// -------------------------------------------------------------
941
    function fSpecial($m)
942
    {
943
        // A special block like notextile or code
944
      @list(, $before, $text, $after) = $m;
945
        return $before.$this->shelve($this->encode_html($text)).$after;
946
    }
947
948
// -------------------------------------------------------------
949
    function noTextile($text)
950
    {
951
         $text = $this->doSpecial($text, '<notextile>', '</notextile>', 'fTextile');
952
         return $this->doSpecial($text, '==', '==', 'fTextile');
953
954
    }
955
956
// -------------------------------------------------------------
957
    function fTextile($m)
958
    {
959
        @list(, $before, $notextile, $after) = $m;
960
        #$notextile = str_replace(array_keys($modifiers), array_values($modifiers), $notextile);
961
        return $before.$this->shelve($notextile).$after;
962
    }
963
964
// -------------------------------------------------------------
965
    function footnoteRef($text)
966
    {
967
        return preg_replace('/\b\[([0-9]+)\](\s)?/Ue',
968
            '$this->footnoteID(\'\1\',\'\2\')', $text);
969
    }
970
971
// -------------------------------------------------------------
972
    function footnoteID($id, $t)
973
    {
974
        if (empty($this->fn[$id]))
975
            $this->fn[$id] = uniqid(rand());
976
        $fnid = $this->fn[$id];
977
        return '<sup class="footnote"><a href="#fn'.$fnid.'">'.$id.'</a></sup>'.$t;
978
    }
979
980
// -------------------------------------------------------------
981
    function glyphs($text)
982
    {
983
984
        // fix: hackish
985
        $text = preg_replace('/"\z/', "\" ", $text);
986
        $pnc = '[[:punct:]]';
987
988
        $glyph_search = array(
989
            '/(\w)\'(\w)/',                                      // apostrophe's
990
            '/(\s)\'(\d+\w?)\b(?!\')/',                          // back in '88
991
            '/(\S)\'(?=\s|'.$pnc.'|<|$)/',                       //  single closing
992
            '/\'/',                                              //  single opening
993
            '/(\S)\"(?=\s|'.$pnc.'|<|$)/',                       //  double closing
994
            '/"/',                                               //  double opening
995
            '/\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/',        //  3+ uppercase acronym
996
            '/\b([A-Z][A-Z\'\-]+[A-Z])(?=[\s.,\)>]|$)/',         //  3+ uppercase
997
            '/\b( )?\.{3}/',                                     //  ellipsis
998
            '/(\s?)--(\s?)/',                                    //  em dash
999
            '/\s-(?:\s|$)/',                                     //  en dash
1000
            '/(\d+)( ?)x( ?)(?=\d+)/',                           //  dimension sign
1001
            '/(\b ?|\s|^)[([]TM[])]/i',                          //  trademark
1002
            '/(\b ?|\s|^)[([]R[])]/i',                           //  registered
1003
            '/(\b ?|\s|^)[([]C[])]/i',                           //  copyright
1004
         );
1005
1006
        extract($this->glyph, EXTR_PREFIX_ALL, 'txt');
1007
1008
        $glyph_replace = array(
1009
            '$1'.$txt_apostrophe.'$2',           // apostrophe's
1010
            '$1'.$txt_apostrophe.'$2',           // back in '88
1011
            '$1'.$txt_quote_single_close,        //  single closing
1012
            $txt_quote_single_open,              //  single opening
1013
            '$1'.$txt_quote_double_close,        //  double closing
1014
            $txt_quote_double_open,              //  double opening
1015
            '<acronym title="$2">$1</acronym>',  //  3+ uppercase acronym
1016
            '<span class="caps">$1</span>',      //  3+ uppercase
1017
            '$1'.$txt_ellipsis,                  //  ellipsis
1018
            '$1'.$txt_emdash.'$2',               //  em dash
1019
            ' '.$txt_endash.' ',                 //  en dash
1020
            '$1$2'.$txt_dimension.'$3',          //  dimension sign
1021
            '$1'.$txt_trademark,                 //  trademark
1022
            '$1'.$txt_registered,                //  registered
1023
            '$1'.$txt_copyright,                 //  copyright
1024
         );
1025
1026
         $text = preg_split("/(<.*>)/U", $text, -1, PREG_SPLIT_DELIM_CAPTURE);
1027
         foreach($text as $line) {
1028
             if (!preg_match("/<.*>/", $line)) {
1029
                 $line = preg_replace($glyph_search, $glyph_replace, $line);
1030
             }
1031
              $glyph_out[] = $line;
1032
         }
1033
         return join('', $glyph_out);
1034
    }
1035
1036
// -------------------------------------------------------------
1037
    function iAlign($in)
1038
    {
1039
        $vals = array(
1040
            '<' => 'left',
1041
            '=' => 'center',
1042
            '>' => 'right');
1043
        return (isset($vals[$in])) ? $vals[$in] : '';
1044
    }
1045
1046
// -------------------------------------------------------------
1047
    function hAlign($in)
1048
    {
1049
        $vals = array(
1050
            '<'  => 'left',
1051
            '='  => 'center',
1052
            '>'  => 'right',
1053
            '<>' => 'justify');
1054
        return (isset($vals[$in])) ? $vals[$in] : '';
1055
    }
1056
1057
// -------------------------------------------------------------
1058
    function vAlign($in)
1059
    {
1060
        $vals = array(
1061
            '^' => 'top',
1062
            '-' => 'middle',
1063
            '~' => 'bottom');
1064
        return (isset($vals[$in])) ? $vals[$in] : '';
1065
    }
1066
1067
// -------------------------------------------------------------
1068
// NOTE: deprecated
1069
    function encode_high($text, $charset = "UTF-8")
1070
    {
1071
        return mb_encode_numericentity($text, $this->cmap(), $charset);
1072
    }
1073
1074
// -------------------------------------------------------------
1075
// NOTE: deprecated
1076
    function decode_high($text, $charset = "UTF-8")
1077
    {
1078
        return mb_decode_numericentity($text, $this->cmap(), $charset);
1079
    }
1080
1081
// -------------------------------------------------------------
1082
// NOTE: deprecated
1083
    function cmap()
1084
    {
1085
        $f = 0xffff;
1086
        $cmap = array(
1087
            0x0080, 0xffff, 0, $f);
1088
        return $cmap;
1089
    }
1090
1091
// -------------------------------------------------------------
1092
    function encode_html($str, $quotes=1)
1093
    {
1094
        $a = array(
1095
            '&' => '&#38;',
1096
            '<' => '&#60;',
1097
            '>' => '&#62;',
1098
        );
1099
        if ($quotes) $a = $a + array(
1100
            "'" => '&#39;',
1101
            '"' => '&#34;',
1102
        );
1103
1104
        return strtr($str, $a);
1105
    }
1106
    
1107
// -------------------------------------------------------------
1108
    function r_encode_html($str, $quotes=1)
1109
    {
1110
        // in restricted mode, input has already been escaped
1111
        if ($this->restricted)
1112
            return $str;
1113
        return $this->encode_html($str, $quotes);
1114
    }
1115
1116
// -------------------------------------------------------------
1117
    function textile_popup_help($name, $helpvar, $windowW, $windowH)
1118
    {
1119
        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 />';
1120
1121
        return $out;
1122
    }
1123
1124
// -------------------------------------------------------------
1125
// NOTE: deprecated
1126
    function txtgps($thing)
1127
    {
1128
        if (isset($_POST[$thing])) {
1129
            if (get_magic_quotes_gpc()) {
1130
                return stripslashes($_POST[$thing]);
1131
            }
1132
            else {
1133
                return $_POST[$thing];
1134
            }
1135
        }
1136
        else {
1137
            return '';
1138
        }
1139
    }
1140
1141
// -------------------------------------------------------------
1142
// NOTE: deprecated
1143
    function dump()
1144
    {
1145
        foreach (func_get_args() as $a)
1146
            echo "\n<pre>",(is_array($a)) ? print_r($a) : $a, "</pre>\n";
1147
    }
1148
1149
// -------------------------------------------------------------
1150
1151
    function blockLite($text)
1152
    {
1153
        $this->btag = array('bq', 'p');
1154
        return $this->block($text."\n\n");
1155
    }
1156
1157
1158
} // end class
1159
1160
?>