Project

General

Profile

Statistics
| Branch: | Revision:

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

History | View | Annotate | Download (37.9 KB)

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: 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
?>