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