Release diogenes-0.9.22
[diogenes.git] / include / classTextile.php
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 $HeadURL: http://svn.textpattern.com/development/4.0/textpattern/lib/classTextile.php $
13 $LastChangedRevision: 1072 $
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 beta
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 _____________
32 L I C E N S E
33
34 Redistribution and use in source and binary forms, with or without
35 modification, are permitted provided that the following conditions are met:
36
37 * Redistributions of source code must retain the above copyright notice,
38 this list of conditions and the following disclaimer.
39
40 * Redistributions in binary form must reproduce the above copyright notice,
41 this list of conditions and the following disclaimer in the documentation
42 and/or other materials provided with the distribution.
43
44 * Neither the name Textile nor the names of its contributors may be used to
45 endorse or promote products derived from this software without specific
46 prior written permission.
47
48 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
49 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
50 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
51 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
52 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
53 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
54 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
55 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
56 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
57 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
58 POSSIBILITY OF SUCH DAMAGE.
59
60 _________
61 U S A G E
62
63 Block modifier syntax:
64
65 Header: h(1-6).
66 Paragraphs beginning with 'hn. ' (where n is 1-6) are wrapped in header tags.
67 Example: h1. Header... -> <h1>Header...</h1>
68
69 Paragraph: p. (also applied by default)
70 Example: p. Text -> <p>Text</p>
71
72 Blockquote: bq.
73 Example: bq. Block quotation... -> <blockquote>Block quotation...</blockquote>
74
75 Blockquote with citation: bq.:http://citation.url
76 Example: bq.:http://textism.com/ Text...
77 -> <blockquote cite="http://textism.com">Text...</blockquote>
78
79 Footnote: fn(1-100).
80 Example: fn1. Footnote... -> <p id="fn1">Footnote...</p>
81
82 Numeric list: #, ##
83 Consecutive paragraphs beginning with # are wrapped in ordered list tags.
84 Example: <ol><li>ordered list</li></ol>
85
86 Bulleted list: *, **
87 Consecutive paragraphs beginning with * are wrapped in unordered list tags.
88 Example: <ul><li>unordered list</li></ul>
89
90 Phrase modifier syntax:
91
92 _emphasis_ -> <em>emphasis</em>
93 __italic__ -> <i>italic</i>
94 *strong* -> <strong>strong</strong>
95 **bold** -> <b>bold</b>
96 ??citation?? -> <cite>citation</cite>
97 -deleted text- -> <del>deleted</del>
98 +inserted text+ -> <ins>inserted</ins>
99 ^superscript^ -> <sup>superscript</sup>
100 ~subscript~ -> <sub>subscript</sub>
101 @code@ -> <code>computer code</code>
102 %(bob)span% -> <span class="bob">span</span>
103
104 ==notextile== -> leave text alone (do not format)
105
106 "linktext":url -> <a href="url">linktext</a>
107 "linktext(title)":url -> <a href="url" title="title">linktext</a>
108
109 !imageurl! -> <img src="imageurl" />
110 !imageurl(alt text)! -> <img src="imageurl" alt="alt text" />
111 !imageurl!:linkurl -> <a href="linkurl"><img src="imageurl" /></a>
112
113 ABC(Always Be Closing) -> <acronym title="Always Be Closing">ABC</acronym>
114
115
116 Table syntax:
117
118 Simple tables:
119
120 |a|simple|table|row|
121 |And|Another|table|row|
122
123 |_. A|_. table|_. header|_.row|
124 |A|simple|table|row|
125
126 Tables with attributes:
127
128 table{border:1px solid black}.
129 {background:#ddd;color:red}. |{}| | | |
130
131
132 Applying Attributes:
133
134 Most anywhere Textile code is used, attributes such as arbitrary css style,
135 css classes, and ids can be applied. The syntax is fairly consistent.
136
137 The following characters quickly alter the alignment of block elements:
138
139 < -> left align ex. p<. left-aligned para
140 > -> right align h3>. right-aligned header 3
141 = -> centred h4=. centred header 4
142 <> -> justified p<>. justified paragraph
143
144 These will change vertical alignment in table cells:
145
146 ^ -> top ex. |^. top-aligned table cell|
147 - -> middle |-. middle aligned|
148 ~ -> bottom |~. bottom aligned cell|
149
150 Plain (parentheses) inserted between block syntax and the closing dot-space
151 indicate classes and ids:
152
153 p(hector). paragraph -> <p class="hector">paragraph</p>
154
155 p(#fluid). paragraph -> <p id="fluid">paragraph</p>
156
157 (classes and ids can be combined)
158 p(hector#fluid). paragraph -> <p class="hector" id="fluid">paragraph</p>
159
160 Curly {brackets} insert arbitrary css style
161
162 p{line-height:18px}. paragraph -> <p style="line-height:18px">paragraph</p>
163
164 h3{color:red}. header 3 -> <h3 style="color:red">header 3</h3>
165
166 Square [brackets] insert language attributes
167
168 p[no]. paragraph -> <p lang="no">paragraph</p>
169
170 %[fr]phrase% -> <span lang="fr">phrase</span>
171
172 Usually Textile block element syntax requires a dot and space before the block
173 begins, but since lists don't, they can be styled just using braces
174
175 #{color:blue} one -> <ol style="color:blue">
176 # big <li>one</li>
177 # list <li>big</li>
178 <li>list</li>
179 </ol>
180
181 Using the span tag to style a phrase
182
183 It goes like this, %{color:red}the fourth the fifth%
184 -> It goes like this, <span style="color:red">the fourth the fifth</span>
185
186 */
187
188 class Textile
189 {
190 var $hlgn;
191 var $vlgn;
192 var $clas;
193 var $lnge;
194 var $styl;
195 var $cspn;
196 var $rspn;
197 var $a;
198 var $s;
199 var $c;
200 var $pnct;
201 var $rel;
202 var $fn;
203
204 // -------------------------------------------------------------
205 function Textile()
206 {
207 $this->hlgn = "(?:\<(?!>)|(?<!<)\>|\<\>|\=|[()]+)";
208 $this->vlgn = "[\-^~]";
209 $this->clas = "(?:\([^)]+\))";
210 $this->lnge = "(?:\[[^]]+\])";
211 $this->styl = "(?:\{[^}]+\})";
212 $this->cspn = "(?:\\\\\d+)";
213 $this->rspn = "(?:\/\d+)";
214 $this->a = "(?:{$this->hlgn}?{$this->vlgn}?|{$this->vlgn}?{$this->hlgn}?)";
215 $this->s = "(?:{$this->cspn}?{$this->rspn}?|{$this->rspn}?{$this->cspn}?)";
216 $this->c = "(?:{$this->clas}?{$this->styl}?{$this->lnge}?|{$this->styl}?{$this->lnge}?{$this->clas}?|{$this->lnge}?{$this->styl}?{$this->clas}?)";
217 $this->pnct = '[\!"#\$%&\'()\*\+,\-\./:;<=>\?@\[\\\]\^_`{\|}\~]';
218
219 }
220
221 // -------------------------------------------------------------
222 function TextileThis($text, $lite='', $encode='', $noimage='', $strict='', $rel='')
223 {
224 if ($rel)
225 $this->rel = ' rel="'.$rel.'" ';
226
227 $text = $this->incomingEntities($text);
228
229 if ($encode) {
230 $text = str_replace("x%x%", "&#38;", $text);
231 return $text;
232 } else {
233
234 if(!$strict) {
235 $text = $this->fixEntities($text);
236 $text = $this->cleanWhiteSpace($text);
237 }
238
239 $text = $this->getRefs($text);
240
241 $text = $this->noTextile($text);
242 $text = $this->links($text);
243 if (!$noimage) {
244 $text = $this->image($text);
245 }
246 $text = $this->code($text);
247 $text = $this->span($text);
248 $text = $this->superscript($text);
249 $text = $this->footnoteRef($text);
250 $text = $this->glyphs($text);
251 $text = $this->retrieve($text);
252
253 if (!$lite) {
254 $text = $this->lists($text);
255 $text = $this->table($text);
256 $text = $this->block($text);
257 }
258
259 // clean up <notextile>
260 $text = preg_replace('/<\/?notextile>/', "", $text);
261
262 // turn the temp char back to an ampersand entity
263 $text = str_replace("x%x%", "&#38;", $text);
264
265 // just to be tidy
266 $text = str_replace("<br />", "<br />\n", $text);
267
268 return $text;
269 }
270 }
271
272 // -------------------------------------------------------------
273 function pba($in, $element = "") // "parse block attributes"
274 {
275 $style = '';
276 $class = '';
277 $lang = '';
278 $colspan = '';
279 $rowspan = '';
280 $id = '';
281 $atts = '';
282
283 if (!empty($in)) {
284 $matched = $in;
285 if ($element == 'td') {
286 if (preg_match("/\\\\(\d+)/", $matched, $csp)) $colspan = $csp[1];
287 if (preg_match("/\/(\d+)/", $matched, $rsp)) $rowspan = $rsp[1];
288
289 if (preg_match("/($this->vlgn)/", $matched, $vert))
290 $style[] = "vertical-align:" . $this->vAlign($vert[1]) . ";";
291 }
292
293 if (preg_match("/\{([^}]*)\}/", $matched, $sty)) {
294 $style[] = $sty[1] . ';';
295 $matched = str_replace($sty[0], '', $matched);
296 }
297
298 if (preg_match("/\[([^)]+)\]/U", $matched, $lng)) {
299 $lang = $lng[1];
300 $matched = str_replace($lng[0], '', $matched);
301 }
302
303 if (preg_match("/\(([^()]+)\)/U", $matched, $cls)) {
304 $class = $cls[1];
305 $matched = str_replace($cls[0], '', $matched);
306 }
307
308 if (preg_match("/([(]+)/", $matched, $pl)) {
309 $style[] = "padding-left:" . strlen($pl[1]) . "em;";
310 $matched = str_replace($pl[0], '', $matched);
311 }
312
313 if (preg_match("/([)]+)/", $matched, $pr)) {
314 // $this->dump($pr);
315 $style[] = "padding-right:" . strlen($pr[1]) . "em;";
316 $matched = str_replace($pr[0], '', $matched);
317 }
318
319 if (preg_match("/($this->hlgn)/", $matched, $horiz))
320 $style[] = "text-align:" . $this->hAlign($horiz[1]) . ";";
321
322 if (preg_match("/^(.*)#(.*)$/", $class, $ids)) {
323 $id = $ids[2];
324 $class = $ids[1];
325 }
326
327 return join('',array(
328 ($style) ? ' style="' . join("", $style) .'"':'',
329 ($class) ? ' class="' . $class .'"':'',
330 ($lang) ? ' lang="' . $lang .'"':'',
331 ($id) ? ' id="' . $id .'"':'',
332 ($colspan) ? ' colspan="' . $colspan .'"':'',
333 ($rowspan) ? ' rowspan="' . $rowspan .'"':''
334 ));
335 }
336 return '';
337 }
338
339 // -------------------------------------------------------------
340 function table($text)
341 {
342 $text = $text . "\n\n";
343 return preg_replace_callback("/^(?:table(_?{$this->s}{$this->a}{$this->c})\. ?\n)?^({$this->a}{$this->c}\.? ?\|.*\|)\n\n/smU",
344 array(&$this, "fTable"), $text);
345 }
346
347 // -------------------------------------------------------------
348 function fTable($matches)
349 {
350 $tatts = $this->pba($matches[1], 'table');
351
352 foreach(preg_split("/\|$/m", $matches[2], -1, PREG_SPLIT_NO_EMPTY) as $row) {
353 if (preg_match("/^($this->a$this->c\. )(.*)/m", $row, $rmtch)) {
354 $ratts = $this->pba($rmtch[1], 'tr');
355 $row = $rmtch[2];
356 } else $ratts = '';
357
358 foreach(explode("|", $row) as $cell) {
359 $ctyp = "d";
360 if (preg_match("/^_/", $cell)) $ctyp = "h";
361 if (preg_match("/^(_?$this->s$this->a$this->c\. )(.*)/", $cell, $cmtch)) {
362 $catts = $this->pba($cmtch[1], 'td');
363 $cell = $cmtch[2];
364 } else $catts = '';
365
366 if (trim($cell) != '')
367 $cells[] = "\t\t\t<t$ctyp$catts>$cell</t$ctyp>";
368 }
369 $rows[] = "\t\t<tr$ratts>\n" . join("\n", $cells) . "\n\t\t</tr>";
370 unset($cells, $catts);
371 }
372 return "\t<table$tatts>\n" . join("\n", $rows) . "\n\t</table>\n\n";
373 }
374
375 // -------------------------------------------------------------
376 function lists($text)
377 {
378 return preg_replace_callback("/^([#*]+$this->c .*)$(?![^#*])/smU", array(&$this, "fList"), $text);
379 }
380
381 // -------------------------------------------------------------
382 function fList($m)
383 {
384 $text = explode("\n", $m[0]);
385 foreach($text as $line) {
386 $nextline = next($text);
387 if (preg_match("/^([#*]+)($this->a$this->c) (.*)$/s", $line, $m)) {
388 list(, $tl, $atts, $content) = $m;
389 $nl = preg_replace("/^([#*]+)\s.*/", "$1", $nextline);
390 if (!isset($lists[$tl])) {
391 $lists[$tl] = true;
392 $atts = $this->pba($atts);
393 $line = "\t<" . $this->lT($tl) . "l$atts>\n\t\t<li>" . $content;
394 } else {
395 $line = "\t\t<li>" . $content;
396 }
397
398 if(strlen($nl) <= strlen($tl)) $line .= "</li>";
399 foreach(array_reverse($lists) as $k => $v) {
400 if(strlen($k) > strlen($nl)) {
401 $line .= "\n\t</" . $this->lT($k) . "l>";
402 if(strlen($k) > 1)
403 $line .= "</li>";
404 unset($lists[$k]);
405 }
406 }
407 }
408 $out[] = $line;
409 }
410 return join("\n", $out);
411 }
412
413 // -------------------------------------------------------------
414 function lT($in)
415 {
416 return preg_match("/^#+/", $in) ? 'o' : 'u';
417 }
418
419 // -------------------------------------------------------------
420 function block($text)
421 {
422 $pre = $php = $txp = false;
423 $find = array('bq', 'h[1-6]', 'fn\d+', 'p');
424
425 $text = preg_replace("/(.+)\n(?![#*\s|])/",
426 "$1<br />", $text);
427
428 $text = explode("\n", $text);
429 array_push($text, " ");
430
431 foreach($text as $line) {
432 if (preg_match('/<pre>/i', $line)) {
433 $pre = true;
434 }
435 elseif (preg_match('/<txp:php>/i', $line)) {
436 $php = true;
437 }
438 elseif (preg_match('/^\s*<txp:/i', $line)) {
439 $txp = true;
440 }
441
442
443 foreach($find as $tag) {
444 $line = ($pre == false and $php == false and $txp == false)
445 ? preg_replace_callback("/^($tag)($this->a$this->c)\.(?::(\S+))? (.*)$/",
446 array(&$this, "fBlock"), $line)
447 : $line;
448 }
449
450 $line = (!$php and !$txp) ? preg_replace('/^(?!\t|<\/?pre|<\/?code|$| )(.*)/', "\t<p>$1</p>", $line) : $line;
451
452 $line = ($pre or $php) ? str_replace("<br />", "\n", $line):$line;
453 if (preg_match('/<\/pre>/i', $line)) {
454 $pre = false;
455 }
456 elseif (preg_match('/<\/txp:php>/i', $line)) {
457 $php = false;
458 }
459 if ($txp == true) $txp = false;
460 $out[] = $line;
461 }
462 return join("\n", $out);
463 }
464
465 // -------------------------------------------------------------
466 function fBlock($m)
467 {
468 // $this->dump($m);
469 list(, $tag, $atts, $cite, $content) = $m;
470
471 $atts = $this->pba($atts);
472
473 if (preg_match("/fn(\d+)/", $tag, $fns)) {
474 $tag = 'p';
475 $fnid = empty($this->fn[$fns[1]]) ? $fns[1] : $this->fn[$fns[1]];
476 $atts .= ' id="fn' . $fnid . '"';
477 $content = '<sup>' . $fns[1] . '</sup> ' . $content;
478 }
479
480 $start = "\t<$tag";
481 $end = "</$tag>";
482
483 if ($tag == "bq") {
484 $cite = $this->checkRefs($cite);
485 $cite = ($cite != '') ? ' cite="' . $cite . '"' : '';
486 $start = "\t<blockquote$cite>\n\t\t<p";
487 $end = "</p>\n\t</blockquote>";
488 }
489
490 return "$start$atts>$content$end";
491 }
492
493 // -------------------------------------------------------------
494 function span($text)
495 {
496 $qtags = array('\*\*','\*','\?\?','-','__','_','%','\+','~');
497
498 foreach($qtags as $f) {
499 $text = preg_replace_callback("/
500 (?<=^|\s|[[:punct:]]|[{([])
501 ($f)
502 ($this->c)
503 (?::(\S+))?
504 ([\w<&].*)
505 ([[:punct:];]*)
506 $f
507 (?=[])}]|[[:punct:]]+|\s|$)
508 /xmU", array(&$this, "fSpan"), $text);
509 }
510 return $text;
511 }
512
513 // -------------------------------------------------------------
514 function fSpan($m)
515 {
516 $qtags = array(
517 '*' => 'strong',
518 '**' => 'b',
519 '??' => 'cite',
520 '_' => 'em',
521 '__' => 'i',
522 '-' => 'del',
523 '%' => 'span',
524 '+' => 'ins',
525 '~' => 'sub'
526 );
527
528 list(, $tag, $atts, $cite, $content, $end) = $m;
529 $tag = $qtags[$tag];
530 $atts = $this->pba($atts);
531 $atts .= ($cite != '') ? 'cite="' . $cite . '"' : '';
532
533 $out = "<$tag$atts>$content$end</$tag>";
534
535 // $this->dump($out);
536
537 return $out;
538
539 }
540
541 // -------------------------------------------------------------
542 function links($text)
543 {
544 return preg_replace_callback('/
545 ([\s[{(]|[[:punct:]])? # $pre
546 " # start
547 (' . $this->c . ') # $atts
548 ([^"]+) # $text
549 \s?
550 (?:\(([^)]+)\)(?="))? # $title
551 ":
552 (\S+\b) # $url
553 (\/)? # $slash
554 ([^\w\/;]*) # $post
555 (?=\s|$)
556 /Ux', array(&$this, "fLink"), $text);
557 }
558
559 // -------------------------------------------------------------
560 function fLink($m)
561 {
562 list(, $pre, $atts, $text, $title, $url, $slash, $post) = $m;
563
564 $url = $this->checkRefs($url);
565
566 $atts = $this->pba($atts);
567 $atts .= ($title != '') ? 'title="' . $title . '"' : '';
568
569 $atts = ($atts) ? $this->shelve($atts) : '';
570
571 $parts = parse_url($url);
572 if (empty($parts['host']) and preg_match('/^\w/', @$parts['path']))
573 $url = hu.$url;
574
575 $out = $pre . '<a href="' . $url . $slash . '"' . $atts . $this->rel . '>' . $text . '</a>' . $post;
576
577 // $this->dump($out);
578 return $out;
579
580 }
581
582 // -------------------------------------------------------------
583 function getRefs($text)
584 {
585 return preg_replace_callback("/(?<=^|\s)\[(.+)\]((?:http:\/\/|\/)\S+)(?=\s|$)/U",
586 array(&$this, "refs"), $text);
587 }
588 // -------------------------------------------------------------
589
590 function refs($m)
591 {
592 list(, $flag, $url) = $m;
593 $this->urlrefs[$flag] = $url;
594 return '';
595 }
596
597 // -------------------------------------------------------------
598 function checkRefs($text)
599 {
600 return (isset($this->urlrefs[$text])) ? $this->urlrefs[$text] : $text;
601 }
602
603 // -------------------------------------------------------------
604 function image($text)
605 {
606 return preg_replace_callback("/
607 \! # opening !
608 (\<|\=|\>)? # optional alignment atts
609 ($this->c) # optional style,class atts
610 (?:\. )? # optional dot-space
611 ([^\s(!]+) # presume this is the src
612 \s? # optional space
613 (?:\(([^\)]+)\))? # optional title
614 \! # closing
615 (?::(\S+))? # optional href
616 (?=\s|$) # lookahead: space or end of string
617 /Ux", array(&$this, "fImage"), $text);
618 }
619
620 // -------------------------------------------------------------
621 function fImage($m)
622 {
623 list(, $algn, $atts, $url) = $m;
624 $atts = $this->pba($atts);
625 $atts .= ($algn != '') ? ' align="' . $this->iAlign($algn) . '"' : '';
626 $atts .= (isset($m[4])) ? ' title="' . $m[4] . '"' : '';
627 $atts .= (isset($m[4])) ? ' alt="' . $m[4] . '"' : ' alt=""';
628 $size = @getimagesize($url);
629 if ($size) $atts .= " $size[3]";
630
631 $href = (isset($m[5])) ? $this->checkRefs($m[5]) : '';
632 $url = $this->checkRefs($url);
633
634 $parts = parse_url($url);
635 if (empty($parts['host']) and preg_match('/^\w/', @$parts['path']))
636 $url = hu.$url;
637
638 $out = array(
639 ($href) ? '<a href="' . $href . '">' : '',
640 '<img src="' . $url . '"' . $atts . ' />',
641 ($href) ? '</a>' : ''
642 );
643
644 return join('',$out);
645 }
646
647 // -------------------------------------------------------------
648 function code($text)
649 {
650 return preg_replace_callback("/
651 (?:^|(?<=[\s\(])|([[{])) # before
652 @
653 (?:\|(\w+)\|)? # lang
654 (.+) # code
655 @
656 (?:$|([\]}])|
657 (?=[[:punct:]]{1,2}|
658 \s|$)) # after
659 /Ux", array(&$this, "fCode"), $text);
660 }
661
662 // -------------------------------------------------------------
663 function fCode($m)
664 {
665 @list(, $before, $lang, $code, $after) = $m;
666 $lang = ($lang) ? ' language="' . $lang . '"' : '';
667 return $before . '<code' . $lang . '>' . $code . '</code>' . $after;
668 }
669
670 // -------------------------------------------------------------
671 function shelve($val)
672 {
673 $this->shelf[] = $val;
674 return ' <' . count($this->shelf) . '>';
675 }
676
677 // -------------------------------------------------------------
678 function retrieve($text)
679 {
680 $i = 0;
681 if (isset($this->shelf) && is_array($this->shelf)) {
682 foreach($this->shelf as $r) {
683 $i++;
684 $text = str_replace("<$i>", $r, $text);
685 }
686 }
687 return $text;
688 }
689
690 // -------------------------------------------------------------
691 function incomingEntities($text)
692 {
693 return preg_replace("/&(?![#a-z0-9]+;)/i", "x%x%", $text);
694 }
695
696 // -------------------------------------------------------------
697 function encodeEntities($text)
698 {
699 return (function_exists('mb_encode_numericentity'))
700 ? $this->encode_high($text)
701 : htmlentities($text, ENT_NOQUOTES, "utf-8");
702 }
703
704 // -------------------------------------------------------------
705 function fixEntities($text)
706 {
707 /* de-entify any remaining angle brackets or ampersands */
708 return str_replace(array("&gt;", "&lt;", "&amp;"),
709 array(">", "<", "&"), $text);
710 }
711
712 // -------------------------------------------------------------
713 function cleanWhiteSpace($text)
714 {
715 $out = str_replace(array("\r\n", "\t"), array("\n", ''), $text);
716 $out = preg_replace("/\n{3,}/", "\n\n", $out);
717 $out = preg_replace("/\n *\n/", "\n\n", $out);
718 $out = preg_replace('/"$/', "\" ", $out);
719 return $out;
720 }
721
722 // -------------------------------------------------------------
723 function noTextile($text)
724 {
725 $text = preg_replace_callback('/(^|\s)<notextile>(.*)<\/notextile>(\s|$)?/msU',
726 array(&$this, "fTextile"), $text);
727 return preg_replace_callback('/(^|\s)==(.*)==(\s|$)?/msU',
728 array(&$this, "fTextile"), $text);
729 }
730
731 // -------------------------------------------------------------
732 function fTextile($m)
733 {
734 $modifiers = array(
735 '"' => '&#34;',
736 '%' => '&#37;',
737 '*' => '&#42;',
738 '+' => '&#43;',
739 '-' => '&#45;',
740 '<' => '&#60;',
741 '=' => '&#61;',
742 '>' => '&#62;',
743 '?' => '&#63;',
744 '^' => '&#94;',
745 '_' => '&#95;',
746 '~' => '&#126;',
747 );
748
749 @list(, $before, $notextile, $after) = $m;
750 $notextile = str_replace(array_keys($modifiers), array_values($modifiers), $notextile);
751 return $before . '<notextile>' . $notextile . '</notextile>' . $after;
752 }
753
754 // -------------------------------------------------------------
755 function superscript($text)
756 {
757 return preg_replace('/\^(.*)\^/mU', '<sup>$1</sup>', $text);
758 }
759
760 // -------------------------------------------------------------
761 function footnoteRef($text)
762 {
763 return preg_replace('/\b\[([0-9]+)\](\s)?/Ue',
764 '$this->footnoteID(\'\1\',\'\2\')', $text);
765 }
766
767 // -------------------------------------------------------------
768 function footnoteID($id, $t)
769 {
770 if (empty($this->fn[$id]))
771 $this->fn[$id] = uniqid(rand());
772 $fnid = $this->fn[$id];
773 return '<sup><a href="#fn'.$fnid.'">'.$id.'</a></sup>'.$t;
774 }
775
776 // -------------------------------------------------------------
777 function glyphs($text)
778 {
779 // fix: hackish
780 $text = preg_replace('/"\z/', "\" ", $text);
781 $pnc = '[[:punct:]]';
782
783 $glyph_search = array(
784 '/([^\s[{(>_*])?\'(?(1)|(?=\s|s\b|'.$pnc.'))/', // single closing
785 '/\'/', // single opening
786 '/([^\s[{(>_*])?"(?(1)|(?=\s|'.$pnc.'))/', // double closing
787 '/"/', // double opening
788 '/\b( )?\.{3}/', // ellipsis
789 '/\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/', // 3+ uppercase acronym
790 '/\s?--\s?/', // em dash
791 '/\s-\s/', // en dash
792 '/(\d+) ?x ?(\d+)/', // dimension sign
793 '/\b ?[([]TM[])]/i', // trademark
794 '/\b ?[([]R[])]/i', // registered
795 '/\b ?[([]C[])]/i'); // copyright
796
797 $glyph_replace = array('$1&#8217;$2', // single closing
798 '&#8216;', // single opening
799 '$1&#8221;', // double closing
800 '&#8220;', // double opening
801 '$1&#8230;', // ellipsis
802 '<acronym title="$2">$1</acronym>', // 3+ uppercase acronym
803 '&#8212;', // em dash
804 ' &#8211; ', // en dash
805 '$1&#215;$2', // dimension sign
806 '&#8482;', // trademark
807 '&#174;', // registered
808 '&#169;'); // copyright
809
810 $codepre = false;
811 /* if no html, do a simple search and replace... */
812 if (!preg_match("/<.*>/", $text)) {
813 $text = preg_replace($glyph_search, $glyph_replace, $text);
814 return $text;
815 }
816 else {
817 $text = preg_split("/(<.*>)/U", $text, -1, PREG_SPLIT_DELIM_CAPTURE);
818 foreach($text as $line) {
819 $offtags = ('code|pre|kbd|notextile|txp:php');
820
821 /* matches are off if we're between <code>, <pre> etc. */
822 if (preg_match('/<(' . $offtags . ')>/i', $line)) $codepre = true;
823 if (preg_match('/<\/(' . $offtags . ')>/i', $line)) $codepre = false;
824
825 if (!preg_match("/<.*>/", $line) && $codepre == false) {
826 $line = preg_replace($glyph_search, $glyph_replace, $line);
827 }
828
829 /* do htmlspecial if between <code> */
830 if ($codepre == true) {
831 $line = htmlspecialchars($line, ENT_NOQUOTES, "UTF-8");
832 $line = preg_replace('/&lt;(\/?' . $offtags . ')&gt;/', "<$1>", $line);
833 $line = str_replace("&amp;#","&#",$line);
834 }
835
836 $glyph_out[] = $line;
837 }
838 return join('', $glyph_out);
839 }
840 }
841
842 // -------------------------------------------------------------
843 function iAlign($in)
844 {
845 $vals = array(
846 '<' => 'left',
847 '=' => 'center',
848 '>' => 'right');
849 return (isset($vals[$in])) ? $vals[$in] : '';
850 }
851
852 // -------------------------------------------------------------
853 function hAlign($in)
854 {
855 $vals = array(
856 '<' => 'left',
857 '=' => 'center',
858 '>' => 'right',
859 '<>' => 'justify');
860 return (isset($vals[$in])) ? $vals[$in] : '';
861 }
862
863 // -------------------------------------------------------------
864 function vAlign($in)
865 {
866 $vals = array(
867 '^' => 'top',
868 '-' => 'middle',
869 '~' => 'bottom');
870 return (isset($vals[$in])) ? $vals[$in] : '';
871 }
872
873 // -------------------------------------------------------------
874 function encode_high($text, $charset = "UTF-8")
875 {
876 return mb_encode_numericentity($text, $this->cmap(), $charset);
877 }
878
879 // -------------------------------------------------------------
880 function decode_high($text, $charset = "UTF-8")
881 {
882 return mb_decode_numericentity($text, $this->cmap(), $charset);
883 }
884
885 // -------------------------------------------------------------
886 function cmap()
887 {
888 $f = 0xffff;
889 $cmap = array(
890 0x0080, 0xffff, 0, $f);
891 return $cmap;
892 }
893
894 // -------------------------------------------------------------
895 function textile_popup_help($name, $helpvar, $windowW, $windowH)
896 {
897 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 />';
898
899 return $out;
900 }
901
902 // -------------------------------------------------------------
903 function txtgps($thing)
904 {
905 if (isset($_POST[$thing])) {
906 if (get_magic_quotes_gpc()) {
907 return stripslashes($_POST[$thing]);
908 }
909 else {
910 return $_POST[$thing];
911 }
912 }
913 else {
914 return '';
915 }
916 }
917
918 // -------------------------------------------------------------
919 function dump()
920 {
921 foreach (func_get_args() as $a)
922 echo "\n<pre>",(is_array($a)) ? print_r($a) : $a, "</pre>\n";
923 }
924
925 // -------------------------------------------------------------
926 function blockLite($text)
927 {
928 $find = array('bq', 'p');
929
930 $text = preg_replace("/(.+)\n(?![#*\s|])/",
931 "$1<br />", $text);
932
933 $text = explode("\n", $text);
934 array_push($text, " ");
935
936 foreach($text as $line) {
937
938 foreach($find as $tag) {
939 $line = preg_replace_callback("/^($tag)($this->a$this->c)\.(?::(\S+))? (.*)$/",
940 array(&$this, "fBlock"), $line);
941 }
942
943 $line = preg_replace('/^(?!\t|<\/?pre|<\/?code|$| )(.*)/', "\t<p>$1</p>", $line);
944 $out[] = $line;
945 }
946 return join("\n", $out);
947 }
948
949
950 } // end class
951
952 ?>