1<?php
2
3/*
4
5This is Textile
6A Humane Web Text Generator
7
8Version 2.0 beta
98 July, 2003
10
11Copyright (c) 2003, Dean Allen, www.textism.com
12All rights reserved.
13
14_______
15LICENSE
16
17Redistribution and use in source and binary forms, with or without
18modification, are permitted provided that the following conditions are met:
19
20* Redistributions of source code must retain the above copyright notice,
21  this list of conditions and the following disclaimer.
22
23* Redistributions in binary form must reproduce the above copyright notice,
24  this list of conditions and the following disclaimer in the documentation
25  and/or other materials provided with the distribution.
26
27* Neither the name Textile nor the names of its contributors may be used to
28  endorse or promote products derived from this software without specific
29  prior written permission.
30
31THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
32AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
33IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
34ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
35LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
36CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
37SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
38INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
39CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
41POSSIBILITY OF SUCH DAMAGE.
42
43_____________
44USING TEXTILE
45
46Block modifier syntax:
47
48    Header: h(1-6).
49    Paragraphs beginning with 'hn. ' (where n is 1-6) are wrapped in header tags.
50    Example: h1. Header... -> <h1>Header...</h1>
51
52    Paragraph: p. (also applied by default)
53    Example: p. Text -> <p>Text</p>
54
55    Blockquote: bq.
56    Example: bq. Block quotation... -> <blockquote>Block quotation...</blockquote>
57
58    Blockquote with citation: bq.:http://citation.url
59    Example: bq.:http://textism.com/ Text...
60    ->    <blockquote cite="http://textism.com">Text...</blockquote>
61
62    Footnote: fn(1-100).
63    Example: fn1. Footnote... -> <p id="fn1">Footnote...</p>
64
65    Numeric list: #, ##
66    Consecutive paragraphs beginning with # are wrapped in ordered list tags.
67    Example: <ol><li>ordered list</li></ol>
68
69    Bulleted list: *, **
70    Consecutive paragraphs beginning with * are wrapped in unordered list tags.
71    Example: <ul><li>unordered list</li></ul>
72
73Phrase modifier syntax:
74
75            _emphasis_   ->   <em>emphasis</em>
76            __italic__   ->   <i>italic</i>
77              *strong*   ->   <strong>strong</strong>
78              **bold**   ->   <b>bold</b>
79          ??citation??   ->   <cite>citation</cite>
80        -deleted text-   ->   <del>deleted</del>
81       +inserted text+   ->   <ins>inserted</ins>
82         ^superscript^   ->   <sup>superscript</sup>
83           ~subscript~   ->   <sub>subscript</sub>
84                @code@   ->   <code>computer code</code>
85           %(bob)span%   ->   <span class="bob">span</span>
86
87         ==notextile==   ->   leave text alone (do not format)
88
89        "linktext":url   ->   <a href="url">linktext</a>
90 "linktext(title)":url   ->   <a href="url" title="title">linktext</a>
91
92            !imageurl!   ->   <img src="imageurl" />
93  !imageurl(alt text)!   ->   <img src="imageurl" alt="alt text" />
94    !imageurl!:linkurl   ->   <a href="linkurl"><img src="imageurl" /></a>
95
96ABC(Always Be Closing)   ->   <acronym title="Always Be Closing">ABC</acronym>
97
98
99Table syntax:
100
101    Simple tables:
102
103        |a|simple|table|row|
104        |And|Another|table|row|
105
106        |_. A|_. table|_. header|_.row|
107        |A|simple|table|row|
108
109    Tables with attributes:
110
111        table{border:1px solid black}.
112        {background:#ddd;color:red}. |{}| | | |
113
114
115Applying Attributes:
116
117    Most anywhere Textile code is used, attributes such as arbitrary css style,
118    css classes, and ids can be applied. The syntax is fairly consistent.
119
120    The following characters quickly alter the alignment of block elements:
121
122        <  ->  left align    ex. p<. left-aligned para
123        >  ->  right align       h3>. right-aligned header 3
124        =  ->  centred           h4=. centred header 4
125        <> ->  justified         p<>. justified paragraph
126
127    These will change vertical alignment in table cells:
128
129        ^  ->  top          ex. |^. top-aligned table cell|
130        -  ->  middle           |-. middle aligned|
131        ~  ->  bottom           |~. bottom aligned cell|
132
133    Plain (parentheses) inserted between block syntax and the closing dot-space
134    indicate classes and ids:
135
136        p(hector). paragraph -> <p class="hector">paragraph</p>
137
138        p(#fluid). paragraph -> <p id="fluid">paragraph</p>
139
140        (classes and ids can be combined)
141        p(hector#fluid). paragraph -> <p class="hector" id="fluid">paragraph</p>
142
143    Curly {brackets} insert arbitrary css style
144
145        p{line-height:18px}. paragraph -> <p style="line-height:18px">paragraph</p>
146
147        h3{color:red}. header 3 -> <h3 style="color:red">header 3</h3>
148
149    Square [brackets] insert language attributes
150
151        p[no]. paragraph -> <p lang="no">paragraph</p>
152
153        %[fr]phrase% -> <span lang="fr">phrase</span>
154
155    Usually Textile block element syntax requires a dot and space before the block
156    begins, but since lists don't, they can be styled just using braces
157
158        #{color:blue} one  ->  <ol style="color:blue">
159        # big                   <li>one</li>
160        # list                  <li>big</li>
161                                <li>list</li>
162                               </ol>
163
164    Using the span tag to style a phrase
165
166        It goes like this, %{color:red}the fourth the fifth%
167        -> It goes like this, <span style="color:red">the fourth the fifth</span>
168
169*/
170
171    function myglobals() {
172      global $textile_hlgn, $textile_vlgn, $textile_clas, $textile_lnge, $textile_styl,
173          $textile_cspn, $textile_rspn, $textile_a, $textile_s, $textile_c, $textile_pnct;
174        $textile_hlgn = "(?:\<(?!>)|(?<!<)\>|\<\>|\=|[()]+)";
175        $textile_vlgn = "[\-^~]";
176        $textile_clas = "(?:\([^)]+\))";
177        $textile_lnge = "(?:\[[^]]+\])";
178        $textile_styl = "(?:\{[^}]+\})";
179        $textile_cspn = "(?:\\\\\d+)";
180        $textile_rspn = "(?:\/\d+)";
181        $textile_a = "(?:$textile_hlgn?$textile_vlgn?|$textile_vlgn?$textile_hlgn?)";
182        $textile_s = "(?:$textile_cspn?$textile_rspn?|$textile_rspn?$textile_cspn?)";
183        $textile_c = "(?:$textile_clas?$textile_styl?$textile_lnge?|$textile_styl?$textile_lnge?$textile_clas?|$textile_lnge?$textile_styl?$textile_clas?)";
184        $textile_pnct = '[\!"#\$%&\'()\*\+,\-\./:;<=>\?@\[\\\]\^_`{\|}\~]';
185    }
186
187    myglobals();
188
189    function textile($text,$lite='') {
190
191        if (get_magic_quotes_gpc()==1)
192            $text = stripslashes($text);
193
194        $text = textile_incomingEntities($text);
195        $text = textile_encodeEntities($text);
196        $text = textile_fixEntities($text);
197        $text = textile_cleanWhiteSpace($text);
198
199        $text = textile_getRefs($text);
200
201        $text = textile_noTextile($text);
202        $text = textile_image($text);
203        $text = textile_links($text);
204        $text = textile_code($text);
205        $text = textile_span($text);
206        $text = textile_superscript($text);
207        $text = textile_footnoteRef($text);
208        $text = textile_glyphs($text);
209        $text = textile_retrieve($text);
210
211        if ($lite=='') {
212            $text = textile_lists($text);
213            $text = textile_table($text);
214            $text = textile_block($text);
215        }
216
217            /* clean up <notextile> */
218        $text = preg_replace('/<\/?notextile>/', "",$text);
219
220            /* turn the temp char back to an ampersand entity */
221        $text = str_replace("x%x%","&#38;",$text);
222
223        $text = str_replace("<br />","<br />\n",$text);
224
225        return trim($text);
226    }
227
228// -------------------------------------------------------------
229    function textile_pba($in,$element="") // "parse block attributes"
230    {
231        global $textile_hlgn,$textile_vlgn,$textile_clas,$textile_styl,$textile_cspn,$textile_rspn,$textile_a,$textile_s,$textile_c;
232
233        $textile_style=''; $textile_class=''; $language=''; $textile_colspan=''; $rowspan=''; $id=''; $textile_atts='';
234
235        if (!empty($in)) {
236            $matched = $in;
237            if($element=='td'){
238                if(preg_match("/\\\\(\d+)/",$matched,$textile_csp)) $textile_colspan=$textile_csp[1];
239                if(preg_match("/\/(\d+)/",$matched,$rsp)) $rowspan=$rsp[1];
240
241                if (preg_match("/($textile_vlgn)/",$matched,$vert))
242                    $textile_style[] = "vertical-align:".textile_vAlign($vert[1]).";";
243            }
244
245            if(preg_match("/\{([^}]*)\}/",$matched,$textile_sty)) {
246                $textile_style[]=$textile_sty[1].';';
247                $matched = str_replace($textile_sty[0],'',$matched);
248            }
249
250            if(preg_match("/\[([^)]+)\]/U",$matched,$lng)) {
251                $language=$lng[1];
252                $matched = str_replace($lng[0],'',$matched);
253            }
254
255            if(preg_match("/\(([^()]+)\)/U",$matched,$textile_cls)) {
256                $textile_class=$textile_cls[1];
257                $matched = str_replace($textile_cls[0],'',$matched);
258            }
259
260            if(preg_match("/([(]+)/",$matched,$pl)) {
261                $textile_style[] = "padding-left:".strlen($pl[1])."em;";
262                $matched = str_replace($pl[0],'',$matched);
263            }
264            if(preg_match("/([)]+)/",$matched,$pr)) {
265#                dump($pr);
266                $textile_style[] = "padding-right:".strlen($pr[1])."em;";
267                $matched = str_replace($pr[0],'',$matched);
268            }
269
270            if (preg_match("/($textile_hlgn)/",$matched,$horiz))
271                $textile_style[] = "text-align:".textile_hAlign($horiz[1]).";";
272
273            if (preg_match("/^(.*)#(.*)$/",$textile_class,$ids)) {
274                $id = $ids[2];
275                $textile_class = $ids[1];
276            }
277
278            if($textile_style) $textile_atts.=' style="'.join("",$textile_style).'"';
279            if($textile_class) $textile_atts.=' class="'.$textile_class.'"';
280            if($language) $textile_atts.=' lang="'.$language.'"';
281            if($id) $textile_atts.=' id="'.$id.'"';
282            if($textile_colspan) $textile_atts.=' colspan="'.$textile_colspan.'"';
283            if($rowspan) $textile_atts.=' rowspan="'.$rowspan.'"';
284
285            return $textile_atts;
286        } else {
287            return '';
288        }
289    }
290
291// -------------------------------------------------------------
292    function textile_table($text)
293    {
294        global $textile_a,$textile_c,$textile_s;
295        $text = $text."\n\n";
296        return preg_replace_callback("/^(?:table(_?$textile_s$textile_a$textile_c)\. ?\n)?^($textile_a$textile_c\.? ?\|.*\|)\n\n/smU",
297            "textile_fTable",$text);
298    }
299
300// -------------------------------------------------------------
301    function textile_fTable($matches)
302    {
303        global $textile_s,$textile_a,$textile_c;
304        $tatts = textile_pba($matches[1],'table');
305
306           foreach(preg_split("/\|$/m",$matches[2],-1,PREG_SPLIT_NO_EMPTY) as $row){
307            if (preg_match("/^($textile_a$textile_c\. )(.*)/m",$row,$rmtch)) {
308                $ratts = textile_pba($rmtch[1],'tr');
309                $row = $rmtch[2];
310            } else $ratts = '';
311
312            foreach(explode("|",$row) as $textile_cell){
313                $textile_ctyp = "d";
314                if (preg_match("/^_/",$textile_cell)) $textile_ctyp = "h";
315                if (preg_match("/^(_?$textile_s$textile_a$textile_c\. )(.*)/",$textile_cell,$textile_cmtch)) {
316                    $textile_catts = textile_pba($textile_cmtch[1],'td');
317                    $textile_cell = $textile_cmtch[2];
318                } else $textile_catts = '';
319
320                if(trim($textile_cell)!='')
321                    $textile_cells[] = "\t\t\t<t$textile_ctyp$textile_catts>$textile_cell</t$textile_ctyp>";
322            }
323            $rows[] = "\t\t<tr$ratts>\n".join("\n",$textile_cells)."\n\t\t</tr>";
324            unset($textile_cells,$textile_catts);
325        }
326        return "\t<table$tatts>\n".join("\n",$rows)."\n\t</table>\n\n";
327    }
328
329
330// -------------------------------------------------------------
331    function textile_lists($text)
332    {
333        global $textile_a,$textile_c;
334        return preg_replace_callback("/^([#*]+$textile_c .*)$(?![^#*])/smU","textile_fList",$text);
335    }
336
337// -------------------------------------------------------------
338    function textile_fList($m)
339    {
340        global $textile_a,$textile_c;
341        $text = explode("\n",$m[0]);
342        foreach($text as $nr => $line){
343            $nextline = isset($text[$nr+1]) ? $text[$nr+1] : false;
344            if(preg_match("/^([#*]+)($textile_a$textile_c) (.*)$/s",$line,$m)) {
345                list(,$tl,$textile_atts,$textile_content) = $m;
346                $nl = preg_replace("/^([#*]+)\s.*/","$1",$nextline);
347                if(!isset($lists[$tl])){
348                    $lists[$tl] = true;
349                    $textile_atts = textile_pba($textile_atts);
350                    $line = "\t<".textile_lT($tl)."l$textile_atts>\n\t<li>".$textile_content;
351                } else {
352                    $line = "\t\t<li>".$textile_content;
353                }
354
355                if ($nl===$tl){
356                    $line .= "</li>";
357                } elseif($nl=="*" or $nl=="#") {
358                    $line .= "</li>\n\t</".textile_lT($tl)."l>\n\t</li>";
359                    unset($lists[$tl]);
360                }
361                if (!$nl) {
362                    foreach($lists as $k=>$v){
363                        $line .= "</li>\n\t</".textile_lT($k)."l>";
364                        unset($lists[$k]);
365                    }
366                }
367            }
368            $out[] = $line;
369        }
370        return join("\n",$out);
371    }
372
373// -------------------------------------------------------------
374    function textile_lT($in)
375    {
376        return preg_match("/^#+/",$in) ? 'o' : 'u';
377    }
378
379// -------------------------------------------------------------
380    function textile_block($text)
381    {
382        global $textile_a,$textile_c;
383
384        $pre = false;
385        $find = array('bq','h[1-6]','fn\d+','p');
386
387        $text = preg_replace("/(.+)\n(?![#*\s|])/",
388            "$1<br />", $text);
389
390        $text = explode("\n",$text);
391        array_push($text," ");
392
393        foreach($text as $line) {
394            if (preg_match('/<pre>/i',$line)) { $pre = true; }
395            foreach($find as $tag){
396                $line = ($pre==false)
397                ?    preg_replace_callback("/^($tag)($textile_a$textile_c)\.(?::(\S+))? (.*)$/",
398                        "textile_fBlock",$line)
399                :    $line;
400            }
401
402            $line = preg_replace('/^(?!\t|<\/?pre|<\/?code|$| )(.*)/',"\t<p>$1</p>",$line);
403
404            $line=($pre==true) ? str_replace("<br />","\n",$line):$line;
405            if (preg_match('/<\/pre>/i',$line)) { $pre = false; }
406
407            $out[] = $line;
408        }
409        return join("\n",$out);
410    }
411
412// -------------------------------------------------------------
413    function textile_fBlock($m)
414    {
415#        dump($m);
416        list(,$tag,$textile_atts,$textile_cite,$textile_content) = $m;
417
418        $textile_atts = textile_pba($textile_atts);
419
420        if(preg_match("/fn(\d+)/",$tag,$fns)){
421            $tag = 'p';
422            $textile_atts.= ' id="fn'.$fns[1].'"';
423            $textile_content = '<sup>'.$fns[1].'</sup> '.$textile_content;
424        }
425
426        $textile_start = "\t<$tag";
427        $end = "</$tag>";
428
429        if ($tag=="bq") {
430            $textile_cite = textile_checkRefs($textile_cite);
431            $textile_cite = ($textile_cite!='') ? ' cite="'.$textile_cite.'"' : '';
432            $textile_start = "\t<blockquote$textile_cite>\n\t\t<p";
433            $end = "</p>\n\t</blockquote>";
434        }
435
436        return "$textile_start$textile_atts>$textile_content$end";
437    }
438
439
440// -------------------------------------------------------------
441    function textile_span($text)
442    {
443        global $textile_c,$textile_pnct;
444        $qtags = array('\*\*','\*','\?\?','-','__','_','%','\+','~');
445
446        foreach($qtags as $f) {
447            $text = preg_replace_callback(
448                "/(?<=^|\s|\>|[[:punct:]]|[{(\[])
449                ($f)
450                ($textile_c)
451                (?::(\S+))?
452                (\w.+\w)
453                ([[:punct:]]*)
454                $f
455                (?=[])}]|[[:punct:]]+|\s|$)
456            /xmU","textile_fSpan",$text);
457        }
458        return $text;
459    }
460
461// -------------------------------------------------------------
462    function textile_fSpan($m)
463    {
464#        dump($m);
465        global $textile_c;
466        $qtags = array(
467            '*'   => 'b',
468            '**'  => 'strong',
469            '??'  => 'cite',
470            '_'   => 'em',
471            '__'  => 'i',
472            '-'   => 'del',
473            '%'   => 'span',
474            '+'   => 'ins',
475            '~'   => 'sub');
476
477            list(,$tag,$textile_atts,$textile_cite,$textile_content,$end) = $m;
478            $tag = $qtags[$tag];
479            $textile_atts = textile_pba($textile_atts);
480            $textile_atts.= ($textile_cite!='') ? 'cite="'.$textile_cite.'"' : '';
481
482        return "<$tag$textile_atts>$textile_content$end</$tag>";
483    }
484
485// -------------------------------------------------------------
486    function textile_links($text)
487    {
488        global $textile_c;
489        return preg_replace_callback('/
490            ([\s[{(]|[[:punct:]])?     # $pre
491            ("|&quot;)                          # start
492            ('.$textile_c.')                   # $textile_atts
493            ([^"]+)                  # $text
494            \s?
495            (?:\(([^)]+)\)(?="))?    # $title
496            ("|&quot;):
497            (\S+\b)                    # $url
498            (\/)?                      # $textile_slash
499            ([^\w\/;]*)                # $post
500            (?=\s|$)
501        /Ux',"textile_fLink",$text);
502    }
503
504// -------------------------------------------------------------
505    function textile_fLink($m)
506    {
507        list(,$pre,$textile_atts,$text,$title,$url,$textile_slash,$post) = $m;
508
509        $url = textile_checkRefs($url);
510
511        $textile_atts = textile_pba($textile_atts);
512        $textile_atts.= ($title!='') ? ' title="'.$title.'"' : '';
513
514        $textile_atts = ($textile_atts!='') ? textile_shelve($textile_atts) : '';
515
516        if ($text=='_') {
517            $text = $url;
518        }
519
520        if (preg_match('/^http(s?):\/\//i', $url)) {
521            $textile_atts.=' class="external"';
522        }
523
524        return $pre.'<a href="'.$url.$textile_slash.'"'.$textile_atts.'>'.$text.'</a>'.$post;
525
526    }
527
528// -------------------------------------------------------------
529    function textile_getRefs($text)
530    {
531        return preg_replace_callback("/(?<=^|\s)\[(.+)\]((?:http:\/\/|\/)\S+)(?=\s|$)/U",
532            "textile_refs",$text);
533    }
534
535// -------------------------------------------------------------
536    function textile_refs($m)
537    {
538        list(,$flag,$url) = $m;
539        $GLOBALS['urlrefs'][$flag] = $url;
540        return '';
541    }
542
543// -------------------------------------------------------------
544    function textile_checkRefs($text)
545    {
546        global $urlrefs;
547        return (isset($urlrefs[$text])) ? $urlrefs[$text] : $text;
548    }
549
550// -------------------------------------------------------------
551    function textile_image($text)
552    {
553        global $textile_c;
554        return preg_replace_callback("/
555            \!                   # opening
556            (\<|\=|\>)?          # optional alignment atts
557            ($textile_c)                 # optional style,class atts
558            (?:\. )?             # optional dot-space
559            ([^\s(!]+)           # presume this is the src
560            \s?                  # optional space
561            (?:\(([^\)]+)\))?    # optional title
562            \!                   # closing
563            (?::(\S+))?          # optional href
564            (?=\s|$)             # lookahead: space or end of string
565        /Ux","textile_fImage",$text);
566    }
567
568// -------------------------------------------------------------
569    function textile_fImage($m)
570    {
571        list(,$textile_algn,$textile_atts,$url) = $m;
572        $textile_atts = textile_pba($textile_atts);
573        $textile_atts.= ($textile_algn!='') ? ' align="'.textile_iAlign($textile_algn).'"' : '';
574        $textile_atts.= (isset($m[4])) ? ' title="'.$m[4].'"' : '';
575        $textile_size = @getimagesize($url);
576        if($textile_size) $textile_atts.= " $textile_size[3]";
577
578        $href = (isset($m[5])) ? textile_checkRefs($m[5]) : '';
579        $url = textile_checkRefs($url);
580
581        $out = '';
582        $out.= ($href!='') ? '<a href="'.$href.'">' : '';
583        $out.= '<img src="'.$url.'"'.$textile_atts.' />';
584        $out.= ($href!='') ? '</a>' : '';
585
586        return $out;
587    }
588
589// -------------------------------------------------------------
590    function textile_code($text)
591    {
592        global $textile_pnct;
593        return preg_replace_callback("/
594            (?:^|(?<=[\s\(])|([[{]))         # 1 open bracket?
595            @                                # opening
596            (?:\|(\w+)\|)?                   # 2 language
597            (.+)                             # 3 code
598            @                                # closing
599            (?:$|([\]}])|
600            (?=[[:punct:]]{1,2}|
601            \s))                             # 4 closing bracket?
602        /Ux","textile_fCode",$text);
603    }
604
605// -------------------------------------------------------------
606    function textile_fCode($m)
607    {
608        list(,$before,$lang,$textile_code,$textile_after) = $m;
609        $lang = ($lang!='') ? ' language="'.$lang.'"' : '';
610        return $before.'<code'.$lang.'>'.$textile_code.'</code>'.$textile_after;
611    }
612
613// -------------------------------------------------------------
614    function textile_shelve($val)
615    {
616        $GLOBALS['shelf'][] = $val;
617        return ' <'.count($GLOBALS['shelf']).'>';
618    }
619
620// -------------------------------------------------------------
621    function textile_retrieve($text)
622    {
623        global $textile_shelf;
624          $i = 0;
625        if(is_array($textile_shelf)) {
626        foreach($textile_shelf as $r){
627            $i++;
628            $text = str_replace("<$i>",$r,$text);
629        }
630        }
631            return $text;
632    }
633
634// -------------------------------------------------------------
635    function textile_incomingEntities($text)
636    {
637        /*  turn any incoming ampersands into a dummy character for now.
638            This uses a negative lookahead for alphanumerics followed by a semicolon,
639            implying an incoming html entity, to be skipped */
640
641        return preg_replace("/&(?![#a-z0-9]+;)/i","x%x%",$text);
642    }
643
644// -------------------------------------------------------------
645    function textile_encodeEntities($text)
646    {
647        /*  Convert high and low ascii to entities. If multibyte string functions are
648            available (on by default in php 4.3+), we convert using unicode mapping as
649            defined in the function encode_high(). If not, we use php's nasty
650            built-in htmlentities() */
651
652        return (function_exists('mb_encode_numericentity'))
653        ?    textile_encode_high($text)
654        :    htmlentities($text,ENT_NOQUOTES,"utf-8");
655    }
656
657// -------------------------------------------------------------
658    function textile_fixEntities($text)
659    {
660        /*  de-entify any remaining angle brackets or ampersands */
661        return str_replace(array("&gt;", "&lt;", "&amp;"),
662            array(">", "<", "&"), $text);
663    }
664
665// -------------------------------------------------------------
666    function textile_cleanWhiteSpace($text)
667    {
668        $out = str_replace(array("\r\n","\t"), array("\n",''), $text);
669        $out = preg_replace("/\n{3,}/","\n\n",$out);
670        $out = preg_replace("/\n *\n/","\n\n",$out);
671        $out = preg_replace('/"$/',"\" ", $out);
672        return $out;
673    }
674
675// -------------------------------------------------------------
676    function textile_noTextile($text)
677    {
678        return preg_replace('/(^|\s)==(.*)==(\s|$)?/msU',
679            '$1<notextile>$2</notextile>$3',$text);
680    }
681
682// -------------------------------------------------------------
683    function textile_superscript($text)
684    {
685        return preg_replace('/\^(.*)\^/mU','<sup>$1</sup>',$text);
686    }
687
688// -------------------------------------------------------------
689    function textile_footnoteRef($text)
690    {
691        return preg_replace('/\b\[([0-9]+)\](\s)?/U',
692            '<sup><a href="#fn$1">$1</a></sup>$2',$text);
693    }
694
695// -------------------------------------------------------------
696    function textile_glyphs($text)
697    {
698            // fix: hackish
699        $text = preg_replace('/"\z/',"\" ", $text);
700
701        $glyph_search = array(
702        '/([^\s[{(>])?\'(?(1)|(?=\s|s\b|[[:punct:]]))/',        //  single closing
703        '/\'/',                                                 //  single opening
704        '/([^\s[{(>])?"(?(1)|(?=\s|[[:punct:]]))/',             //  double closing
705        '/"/',                                                  //  double opening
706        '/\b( )?\.{3}/',                                        //  ellipsis
707        '/\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/',           //  3+ uppercase acronym
708        '/(^|[^"][>\s])([A-Z][A-Z0-9 ]{2,})([^<a-z0-9]|$)/',    //  3+ uppercase caps
709        '/\s?--\s?/',                                           //  em dash
710        '/\s-\s/',                                              //  en dash
711        '/(\d+) ?x ?(\d+)/',                                    //  dimension sign
712        '/\b ?[([]TM[])]/i',                                    //  trademark
713        '/\b ?[([]R[])]/i',                                     //  registered
714        '/\b ?[([]C[])]/i');                                    //  copyright
715
716    $glyph_replace = array(
717        '$1&#8217;$2',                          //  single closing
718        '&#8216;',                              //  single opening
719        '$1&#8221;',                            //  double closing
720        '&#8220;',                              //  double opening
721        '$1&#8230;',                            //  ellipsis
722        '<acronym title="$2">$1</acronym>',     //  3+ uppercase acronym
723        '$1<span class="caps">$2</span>$3',     //  3+ uppercase caps
724        '&#8212;',                              //  em dash
725        ' &#8211; ',                            //  en dash
726        '$1&#215;$2',                           //  dimension sign
727        '&#8482;',                              //  trademark
728        '&#174;',                               //  registered
729        '&#169;');                              //  copyright
730
731
732    $textile_codepre = false;
733        /*  if no html, do a simple search and replace... */
734    if (!preg_match("/<.*>/",$text)) {
735        $text = preg_replace($glyph_search,$glyph_replace,$text);
736        return $text;
737    } else {
738        $text = preg_split("/(<.*>)/U",$text,-1,PREG_SPLIT_DELIM_CAPTURE);
739        foreach($text as $line) {
740                $offtags = ('code|pre|kbd|notextile');
741
742                    /*  matches are off if we're between <code>, <pre> etc. */
743                if (preg_match('/<('.$offtags.')>/i',$line)) $textile_codepre = true;
744                if (preg_match('/<\/('.$offtags.')>/i',$line)) $textile_codepre = false;
745
746                if (!preg_match("/<.*>/",$line) && $textile_codepre == false) {
747                    $line = preg_replace($glyph_search,$glyph_replace,$line);
748                }
749
750                    /* do htmlspecial if between <code> */
751                if ($textile_codepre == true) {
752                    $line = htmlspecialchars($line,ENT_NOQUOTES,"UTF-8");
753                    $line = preg_replace('/&lt;(\/?'.$offtags.')&gt;/',"<$1>",$line);
754                }
755
756            $glyph_out[] = $line;
757        }
758        return join('',$glyph_out);
759    }
760    }
761
762// -------------------------------------------------------------
763    function textile_iAlign($in)
764    {
765        $vals = array(
766            '<'=>'left',
767            '='=>'center',
768            '>'=>'right');
769        return (isset($vals[$in])) ? $vals[$in] : '';
770    }
771
772// -------------------------------------------------------------
773    function textile_hAlign($in)
774    {
775        $vals = array(
776            '<'=>'left',
777            '='=>'center',
778            '>'=>'right',
779            '<>'=>'justify');
780        return (isset($vals[$in])) ? $vals[$in] : '';
781    }
782
783// -------------------------------------------------------------
784    function textile_vAlign($in)
785    {
786        $vals = array(
787            '^'=>'top',
788            '-'=>'middle',
789            '~'=>'bottom');
790        return (isset($vals[$in])) ? $vals[$in] : '';
791    }
792
793// -------------------------------------------------------------
794    function textile_encode_high($text,$textile_charset="UTF-8")
795    {
796        $textile_cmap = textile_cmap();
797        return mb_encode_numericentity($text, $textile_cmap, $textile_charset);
798    }
799
800// -------------------------------------------------------------
801    function textile_decode_high($text,$textile_charset="UTF-8")
802    {
803        $textile_cmap = textile_cmap();
804        return mb_decode_numericentity($text, $textile_cmap, $textile_charset);
805    }
806
807// -------------------------------------------------------------
808    function textile_cmap()
809    {
810        $f = 0xffff;
811        $textile_cmap = array(
812         160,  255,  0, $f,
813         402,  402,  0, $f,
814         913,  929,  0, $f,
815         931,  937,  0, $f,
816         945,  969,  0, $f,
817         977,  978,  0, $f,
818         982,  982,  0, $f,
819         8226, 8226, 0, $f,
820         8230, 8230, 0, $f,
821         8242, 8243, 0, $f,
822         8254, 8254, 0, $f,
823         8260, 8260, 0, $f,
824         8465, 8465, 0, $f,
825         8472, 8472, 0, $f,
826         8476, 8476, 0, $f,
827         8482, 8482, 0, $f,
828         8501, 8501, 0, $f,
829         8592, 8596, 0, $f,
830         8629, 8629, 0, $f,
831         8656, 8660, 0, $f,
832         8704, 8704, 0, $f,
833         8706, 8707, 0, $f,
834         8709, 8709, 0, $f,
835         8711, 8713, 0, $f,
836         8715, 8715, 0, $f,
837         8719, 8719, 0, $f,
838         8721, 8722, 0, $f,
839         8727, 8727, 0, $f,
840         8730, 8730, 0, $f,
841         8733, 8734, 0, $f,
842         8736, 8736, 0, $f,
843         8743, 8747, 0, $f,
844         8756, 8756, 0, $f,
845         8764, 8764, 0, $f,
846         8773, 8773, 0, $f,
847         8776, 8776, 0, $f,
848         8800, 8801, 0, $f,
849         8804, 8805, 0, $f,
850         8834, 8836, 0, $f,
851         8838, 8839, 0, $f,
852         8853, 8853, 0, $f,
853         8855, 8855, 0, $f,
854         8869, 8869, 0, $f,
855         8901, 8901, 0, $f,
856         8968, 8971, 0, $f,
857         9001, 9002, 0, $f,
858         9674, 9674, 0, $f,
859         9824, 9824, 0, $f,
860         9827, 9827, 0, $f,
861         9829, 9830, 0, $f,
862         338,  339,  0, $f,
863         352,  353,  0, $f,
864         376,  376,  0, $f,
865         710,  710,  0, $f,
866         732,  732,  0, $f,
867         8194, 8195, 0, $f,
868         8201, 8201, 0, $f,
869         8204, 8207, 0, $f,
870         8211, 8212, 0, $f,
871         8216, 8218, 0, $f,
872         8218, 8218, 0, $f,
873         8220, 8222, 0, $f,
874         8224, 8225, 0, $f,
875         8240, 8240, 0, $f,
876         8249, 8250, 0, $f,
877         8364, 8364, 0, $f);
878        return $textile_cmap;
879    }
880
881
882// -------------------------------------------------------------
883    function textile_popup_help($name,$helpvar,$windowW,$windowH) {
884        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 />';
885    }
886
887// -------------------------------------------------------------
888    function textile_txtgps($thing)
889    {
890        if (isset($_POST[$thing])){
891            if (get_magic_quotes_gpc()==1){
892                return stripslashes($_POST[$thing]);
893            } else {
894                return $_POST[$thing];
895            }
896        } else {
897            return '';
898        }
899    }
900
901
902// -------------------------------------------------------------
903// The following functions are used to detextile html, a process
904// still in development.
905
906
907// -------------------------------------------------------------
908    function textile_detextile($text) {
909
910    $text = preg_replace("/<br \/>\s*/","\n",$text);
911
912    $oktags = array('p','ol','ul','li','i','b','em','strong','span','a','h[1-6]',
913        'table','tr','td','u','del','sup','sub','blockquote');
914
915    foreach($oktags as $tag){
916        $text = preg_replace_callback("/\t*<(".$tag.")\s*([^>]*)>(.*)<\/\\1>/Usi",
917        "textile_processTag",$text);
918    }
919
920        $glyphs = array(
921            '&#8217;'=>'\'',        # single closing
922            '&#8216;'=>'\'',        # single opening
923            '&#8221;'=>'"',         # double closing
924            '&#8220;'=>'"',         # double opening
925            '&#8212;'=>'--',        # em dash
926            '&#8211;'=>' - ',       # en dash
927            '&#215;' =>'x',         # dimension sign
928            '&#8482;'=>'(TM)',      # trademark
929            '&#174;' =>'(R)',       # registered
930            '&#169;' =>'(C)',       # copyright
931            '&#8230;'=>'...'        # ellipsis
932        );
933
934        foreach($glyphs as $f=>$r){
935            $text = str_replace($f,$r,$text);
936        }
937
938        $list = false;
939
940        $text = preg_split("/(<.*>)/U",$text,-1,PREG_SPLIT_DELIM_CAPTURE);
941            foreach($text as $line){
942
943            if ($list == false && preg_match('/<ol/',$line)){
944                $line = "";
945                $list = "o";
946            } else if (preg_match('/<\/ol/',$line)){
947                $line = "";
948                $list = false;
949            } else if ($list == false && preg_match('/<ul/',$line)){
950                $line = "";
951                $list = "u";
952            } else if (preg_match('/<\/ul/',$line)){
953                $line = "";
954                $list = false;
955            } else if ($list == 'o'){
956                $line = preg_replace('/<li.*>/U','# ', $line);
957            } else if ($list == 'u'){
958                $line = preg_replace('/<li.*>/U','* ', $line);
959            }
960            $glyph_out[] = $line;
961        }
962
963        $text = implode('',$glyph_out);
964
965        $text = preg_replace('/^\t* *p\. /m','',$text);
966
967        return textile_decode_high($text);
968    }
969
970
971// -------------------------------------------------------------
972    function textile_processTag($matches)
973    {
974        list($textile_all,$tag,$textile_atts,$textile_content) = $matches;
975        $textile_a = textile_splat($textile_atts);
976#        dump($tag); dump($textile_content); dump($textile_a);
977
978        $phr = array(
979        'em'=>'_',
980        'i'=>'__',
981        'b'=>'**',
982        'strong'=>'*',
983        'cite'=>'??',
984        'del'=>'-',
985        'ins'=>'+',
986        'sup'=>'^',
987        'sub'=>'~',
988        'span'=>'%');
989
990        $blk = array('p','h1','h2','h3','h4','h5','h6');
991
992        if(isset($phr[$tag])) {
993            return $phr[$tag].textile_sci($textile_a).$textile_content.$phr[$tag];
994        } elseif($tag=='blockquote') {
995            return 'bq.'.textile_sci($textile_a).' '.$textile_content;
996        } elseif(in_array($tag,$blk)) {
997            return $tag.textile_sci($textile_a).'. '.$textile_content;
998        } elseif ($tag=='a') {
999            $t = textile_filterAtts($textile_a,array('href','title'));
1000            $out = '"'.$textile_content;
1001            $out.= (isset($t['title'])) ? ' ('.$t['title'].')' : '';
1002            $out.= '":'.$t['href'];
1003            return $out;
1004        } else {
1005            return $textile_all;
1006        }
1007    }
1008
1009// -------------------------------------------------------------
1010    function textile_filterAtts($textile_atts,$ok)
1011    {
1012        foreach($textile_atts as $textile_a) {
1013            if(in_array($textile_a['name'],$ok)) {
1014                if($textile_a['att']!='') {
1015                $out[$textile_a['name']] = $textile_a['att'];
1016                }
1017            }
1018        }
1019#        dump($out);
1020        return $out;
1021    }
1022
1023// -------------------------------------------------------------
1024    function textile_sci($textile_a)
1025    {
1026        $out = '';
1027        foreach($textile_a as $t){
1028            $out.= ($t['name']=='class') ? '(='.$t['att'].')' : '';
1029            $out.= ($t['name']=='id') ? '[='.$t['att'].']' : '';
1030            $out.= ($t['name']=='style') ? '{='.$t['att'].'}' : '';
1031            $out.= ($t['name']=='cite') ? ':'.$t['att'] : '';
1032        }
1033        return $out;
1034    }
1035
1036// -------------------------------------------------------------
1037    function textile_splat($textile_attr)  // returns attributes as an array
1038    {
1039        $textile_arr = array();
1040        $textile_atnm = '';
1041        $mode = 0;
1042
1043        while (strlen($textile_attr) != 0){
1044            $ok = 0;
1045            switch ($mode) {
1046                case 0: // name
1047                    if (preg_match('/^([a-z]+)/i', $textile_attr, $match)) {
1048                        $textile_atnm = $match[1]; $ok = $mode = 1;
1049                        $textile_attr = preg_replace('/^[a-z]+/i', '', $textile_attr);
1050                    }
1051                break;
1052
1053                case 1: // =
1054                    if (preg_match('/^\s*=\s*/', $textile_attr)) {
1055                        $ok = 1; $mode = 2;
1056                        $textile_attr = preg_replace('/^\s*=\s*/', '', $textile_attr);
1057                    break;
1058                    }
1059                    if (preg_match('/^\s+/', $textile_attr)) {
1060                        $ok = 1; $mode = 0;
1061                        $textile_arr[] = array('name'=>$textile_atnm,'whole'=>$textile_atnm,'att'=>$textile_atnm);
1062                        $textile_attr = preg_replace('/^\s+/', '', $textile_attr);
1063                    }
1064                break;
1065
1066                case 2: // value
1067                    if (preg_match('/^("[^"]*")(\s+|$)/', $textile_attr, $match)) {
1068                        $textile_arr[]=array('name' =>$textile_atnm,'whole'=>$textile_atnm.'='.$match[1],
1069                                'att'=>str_replace('"','',$match[1]));
1070                        $ok = 1; $mode = 0;
1071                        $textile_attr = preg_replace('/^"[^"]*"(\s+|$)/', '', $textile_attr);
1072                    break;
1073                    }
1074                    if (preg_match("/^('[^']*')(\s+|$)/", $textile_attr, $match)) {
1075                        $textile_arr[]=array('name' =>$textile_atnm,'whole'=>$textile_atnm.'='.$match[1],
1076                                'att'=>str_replace("'",'',$match[1]));
1077                        $ok = 1; $mode = 0;
1078                        $textile_attr = preg_replace("/^'[^']*'(\s+|$)/", '', $textile_attr);
1079                    break;
1080                    }
1081                    if (preg_match("/^(\w+)(\s+|$)/", $textile_attr, $match)) {
1082                        $textile_arr[]=
1083                            array('name'=>$textile_atnm,'whole'=>$textile_atnm.'="'.$match[1].'"',
1084                                'att'=>$match[1]);
1085                        $ok = 1; $mode = 0;
1086                        $textile_attr = preg_replace("/^\w+(\s+|$)/", '', $textile_attr);
1087                    }
1088                break;
1089            }
1090            if ($ok == 0){
1091                $textile_attr = preg_replace('/^\S*\s*/', '', $textile_attr);
1092                $mode = 0;
1093            }
1094        }
1095        if ($mode == 1) $textile_arr[] =
1096                array ('name'=>$textile_atnm,'whole'=>$textile_atnm.'="'.$textile_atnm.'"','att'=>$textile_atnm);
1097
1098        return $textile_arr;
1099    }
1100?>
1101