| 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%", "&", $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%", "&", $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(">", "<", "&"), |
| 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 | '"' => '"', |
| 736 | '%' => '%', |
| 737 | '*' => '*', |
| 738 | '+' => '+', |
| 739 | '-' => '-', |
| 740 | '<' => '<', |
| 741 | '=' => '=', |
| 742 | '>' => '>', |
| 743 | '?' => '?', |
| 744 | '^' => '^', |
| 745 | '_' => '_', |
| 746 | '~' => '~', |
| 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’$2', // single closing |
| 798 | '‘', // single opening |
| 799 | '$1”', // double closing |
| 800 | '“', // double opening |
| 801 | '$1…', // ellipsis |
| 802 | '<acronym title="$2">$1</acronym>', // 3+ uppercase acronym |
| 803 | '—', // em dash |
| 804 | ' – ', // en dash |
| 805 | '$1×$2', // dimension sign |
| 806 | '™', // trademark |
| 807 | '®', // registered |
| 808 | '©'); // 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('/<(\/?' . $offtags . ')>/', "<$1>", $line); |
| 833 | $line = str_replace("&#","&#",$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 | ?> |