Textpattern PHP Cross Reference Content Management Systems

Source: /textpattern/lib/classTextile.php - 2041 lines - 61825 bytes - Summary - Text - Print

Description: Example: get XHTML from a given Textile-markup string ($string)

   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: https://textpattern.googlecode.com/svn/releases/4.5.4/source/textpattern/lib/classTextile.php $
  13  $LastChangedRevision: 4096 $
  14  */
  15  
  16  /*
  17  
  18  _____________
  19  T E X T I L E
  20  
  21  A Humane Web Text Generator
  22  
  23  Version 2.4.1
  24  
  25  Copyright (c) 2003-2004, Dean Allen <dean@textism.com>
  26  All rights reserved.
  27  
  28  Thanks to Carlo Zottmann <carlo@g-blog.net> for refactoring
  29  Textile's procedural code into a class framework
  30  
  31  Additions and fixes Copyright (c) 2006    Alex Shiels       http://thresholdstate.com/
  32  Additions and fixes Copyright (c) 2010    Stef Dawson       http://stefdawson.com/
  33  Additions and fixes Copyright (c) 2010-12 Netcarver         http://github.com/netcarver
  34  Additions and fixes Copyright (c) 2011    Jeff Soo          http://ipsedixit.net
  35  Additions and fixes Copyright (c) 2012    Robert Wetzlmayr     http://wetzlmayr.com/
  36  
  37  _____________
  38  L I C E N S E
  39  
  40  Redistribution and use in source and binary forms, with or without
  41  modification, are permitted provided that the following conditions are met:
  42  
  43  * Redistributions of source code must retain the above copyright notice,
  44    this list of conditions and the following disclaimer.
  45  
  46  * Redistributions in binary form must reproduce the above copyright notice,
  47    this list of conditions and the following disclaimer in the documentation
  48    and/or other materials provided with the distribution.
  49  
  50  * Neither the name Textile nor the names of its contributors may be used to
  51    endorse or promote products derived from this software without specific
  52    prior written permission.
  53  
  54  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  55  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  56  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  57  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  58  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  59  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  60  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  61  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  62  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  63  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  64  POSSIBILITY OF SUCH DAMAGE.
  65  
  66  _________
  67  U S A G E
  68  
  69  Block modifier syntax:
  70  
  71      Header: h(1-6).
  72      Paragraphs beginning with 'hn. ' (where n is 1-6) are wrapped in header tags.
  73      Example: h1. Header... -> <h1>Header...</h1>
  74  
  75      Paragraph: p. (also applied by default)
  76      Example: p. Text -> <p>Text</p>
  77  
  78      Blockquote: bq.
  79      Example: bq. Block quotation... -> <blockquote>Block quotation...</blockquote>
  80  
  81      Blockquote with citation: bq.:http://citation.url
  82      Example: bq.:http://textism.com/ Text...
  83      ->    <blockquote cite="http://textism.com">Text...</blockquote>
  84  
  85      Footnote: fn(1-100).
  86      Example: fn1. Footnote... -> <p id="fn1">Footnote...</p>
  87  
  88      Numeric list: #, ##
  89      Consecutive paragraphs beginning with # are wrapped in ordered list tags.
  90      Example: <ol><li>ordered list</li></ol>
  91  
  92      Bulleted list: *, **
  93      Consecutive paragraphs beginning with * are wrapped in unordered list tags.
  94      Example: <ul><li>unordered list</li></ul>
  95  
  96      Definition list:
  97          Terms ;, ;;
  98          Definitions :, ::
  99      Consecutive paragraphs beginning with ; or : are wrapped in definition list tags.
 100      Example: <dl><dt>term</dt><dd>definition</dd></dl>
 101  
 102      Redcloth-style Definition list:
 103          - Term1 := Definition1
 104          - Term2 := Extended
 105            definition =:
 106  
 107  Phrase modifier syntax:
 108  
 109             _emphasis_    ->     <em>emphasis</em>
 110             __italic__    ->     <i>italic</i>
 111               *strong*    ->     <strong>strong</strong>
 112               **bold**    ->     <b>bold</b>
 113           ??citation??    ->     <cite>citation</cite>
 114         -deleted text-    ->     <del>deleted</del>
 115        +inserted text+    ->     <ins>inserted</ins>
 116          ^superscript^    ->     <sup>superscript</sup>
 117            ~subscript~    ->     <sub>subscript</sub>
 118                 @code@    ->     <code>computer code</code>
 119            %(bob)span%    ->     <span class="bob">span</span>
 120  
 121          ==notextile==    ->     leave text alone (do not format)
 122  
 123         "linktext":url    ->     <a href="url">linktext</a>
 124   "linktext(title)":url    ->     <a href="url" title="title">linktext</a>
 125              "$":url  ->  <a href="url">url</a>
 126       "$(title)":url  ->  <a href="url" title="title">url</a>
 127  
 128             !imageurl!    ->     <img src="imageurl" />
 129      !imageurl(alt text)!    ->     <img src="imageurl" alt="alt text" />
 130      !imageurl!:linkurl    ->     <a href="linkurl"><img src="imageurl" /></a>
 131  
 132  ABC(Always Be Closing)    ->     <acronym title="Always Be Closing">ABC</acronym>
 133  
 134  
 135  Linked Notes:
 136  ============
 137  
 138      Allows the generation of an automated list of notes with links.
 139  
 140      Linked notes are composed of three parts, a set of named _definitions_, a set of
 141      _references_ to those definitions and one or more _placeholders_ indicating where
 142      the consolidated list of notes is to be placed in your document.
 143  
 144      Definitions.
 145      -----------
 146  
 147      Each note definition must occur in its own paragraph and should look like this...
 148  
 149      note#mynotelabel. Your definition text here.
 150  
 151      You are free to use whatever label you wish after the # as long as it is made up
 152      of letters, numbers, colon(:) or dash(-).
 153  
 154      References.
 155      ----------
 156  
 157      Each note reference is marked in your text like this[#mynotelabel] and
 158      it will be replaced with a superscript reference that links into the list of
 159      note definitions.
 160  
 161      List Placeholder(s).
 162      -------------------
 163  
 164      The note list can go anywhere in your document. You have to indicate where
 165      like this...
 166  
 167      notelist.
 168  
 169      notelist can take attributes (class#id) like this: notelist(class#id).
 170  
 171      By default, the note list will show each definition in the order that they
 172      are referenced in the text by the _references_. It will show each definition with
 173      a full list of backlinks to each reference. If you do not want this, you can choose
 174      to override the backlinks like this...
 175  
 176      notelist(class#id)!.    Produces a list with no backlinks.
 177      notelist(class#id)^.    Produces a list with only the first backlink.
 178  
 179      Should you wish to have a specific definition display backlinks differently to this
 180      then you can override the backlink method by appending a link override to the
 181      _definition_ you wish to customise.
 182  
 183      note#label.    Uses the citelist's setting for backlinks.
 184      note#label!.   Causes that definition to have no backlinks.
 185      note#label^.   Causes that definition to have one backlink (to the first ref.)
 186      note#label*.   Causes that definition to have all backlinks.
 187  
 188      Any unreferenced notes will be left out of the list unless you explicitly state
 189      you want them by adding a '+'. Like this...
 190  
 191      notelist(class#id)!+. Giving a list of all notes without any backlinks.
 192  
 193      You can mix and match the list backlink control and unreferenced links controls
 194      but the backlink control (if any) must go first. Like so: notelist^+. , not
 195      like this: notelist+^.
 196  
 197      Example...
 198          Scientists say[#lavader] the moon is small.
 199  
 200          note#other. An unreferenced note.
 201  
 202          note#lavader(myliclass). "Proof":url of a small moon.
 203  
 204          notelist(myclass#myid)+.
 205  
 206          Would output (the actual IDs used would be randomised)...
 207  
 208          <p>Scientists say<sup><a href="#def_id_1" id="ref_id_1a">1</sup> the moon is small.</p>
 209  
 210          <ol class="myclass" id="myid">
 211              <li class="myliclass"><a href="#ref_id_1a"><sup>a</sup></a><span id="def_id_1"> </span><a href="url">Proof</a> of a small moon.</li>
 212              <li>An unreferenced note.</li>
 213          </ol>
 214  
 215          The 'a b c' backlink characters can be altered too.
 216          For example if you wanted the notes to have numeric backlinks starting from 1:
 217  
 218          notelist:1.
 219  
 220  Table syntax:
 221  
 222      Simple tables:
 223  
 224          |a|simple|table|row|
 225          |And|Another|table|row|
 226          |With an||empty|cell|
 227  
 228          |=. My table caption goes here
 229          |_. A|_. table|_. header|_.row|
 230          |A|simple|table|row|
 231  
 232      Tables with attributes:
 233  
 234          table{border:1px solid black}. My table summary here
 235          {background:#ddd;color:red}. |{}| | | |
 236  
 237      To specify thead / tfoot / tbody groups, add one of these on its own line
 238      above the row(s) you wish to wrap (you may specify attributes before the dot):
 239  
 240          |^.     # thead
 241          |-.     # tbody
 242          |~.     # tfoot
 243  
 244      Column groups:
 245  
 246          |:\3. 100|
 247  
 248          Becomes:
 249              <colgroup span="3" width="100"></colgroup>
 250  
 251          You can omit either or both of the \N or width values. You may also
 252          add cells after the colgroup definition to specify col elements with
 253          span, width, or standard Textile attributes:
 254  
 255          |:. 50|(firstcol). |\2. 250||300|
 256  
 257          Becomes:
 258              <colgroup width="50">
 259                  <col class="firstcol" />
 260                  <col span="2" width="250" />
 261                  <col />
 262                  <col width="300" />
 263              </colgroup>
 264  
 265          (Note that, per the HTML specification, you should not add span
 266          to the colgroup if specifying col elements.)
 267  
 268  Applying Attributes:
 269  
 270      Most anywhere Textile code is used, attributes such as arbitrary css style,
 271      css classes, and ids can be applied. The syntax is fairly consistent.
 272  
 273      The following characters quickly alter the alignment of block elements:
 274  
 275          <  ->  left align     ex. p<. left-aligned para
 276          >  ->  right align         h3>. right-aligned header 3
 277          =  ->  centred             h4=. centred header 4
 278          <> ->  justified         p<>. justified paragraph
 279  
 280      These will change vertical alignment in table cells:
 281  
 282          ^  ->  top           ex. |^. top-aligned table cell|
 283          -  ->  middle           |-. middle aligned|
 284          ~  ->  bottom           |~. bottom aligned cell|
 285  
 286      Plain (parentheses) inserted between block syntax and the closing dot-space
 287      indicate classes and ids:
 288  
 289          p(hector). paragraph -> <p class="hector">paragraph</p>
 290  
 291          p(#fluid). paragraph -> <p id="fluid">paragraph</p>
 292  
 293          (classes and ids can be combined)
 294          p(hector#fluid). paragraph -> <p class="hector" id="fluid">paragraph</p>
 295  
 296      Curly {brackets} insert arbitrary css style
 297  
 298          p{line-height:18px}. paragraph -> <p style="line-height:18px">paragraph</p>
 299  
 300          h3{color:red}. header 3 -> <h3 style="color:red">header 3</h3>
 301  
 302      Square [brackets] insert language attributes
 303  
 304          p[no]. paragraph -> <p lang="no">paragraph</p>
 305  
 306          %[fr]phrase% -> <span lang="fr">phrase</span>
 307  
 308      Usually Textile block element syntax requires a dot and space before the block
 309      begins, but since lists don't, they can be styled just using braces
 310  
 311          #{color:blue} one  ->  <ol style="color:blue">
 312          # big                    <li>one</li>
 313          # list                    <li>big</li>
 314                                  <li>list</li>
 315                                 </ol>
 316  
 317      Using the span tag to style a phrase
 318  
 319          It goes like this, %{color:red}the fourth the fifth%
 320                -> It goes like this, <span style="color:red">the fourth the fifth</span>
 321  
 322  Ordered List Start & Continuation:
 323  
 324      You can control the start attribute of an ordered list like so;
 325  
 326          #5 Item 5
 327          # Item 6
 328  
 329      You can resume numbering list items after some intervening anonymous block like so...
 330  
 331          #_ Item 7
 332          # Item 8
 333  
 334  */
 335  
 336  // define these before including this file to override the standard glyphs
 337  @define('txt_quote_single_open',  '&#8216;');
 338  @define('txt_quote_single_close', '&#8217;');
 339  @define('txt_quote_double_open',  '&#8220;');
 340  @define('txt_quote_double_close', '&#8221;');
 341  @define('txt_apostrophe',         '&#8217;');
 342  @define('txt_prime',              '&#8242;');
 343  @define('txt_prime_double',       '&#8243;');
 344  @define('txt_ellipsis',           '&#8230;');
 345  @define('txt_emdash',             '&#8212;');
 346  @define('txt_endash',             '&#8211;');
 347  @define('txt_dimension',          '&#215;');
 348  @define('txt_trademark',          '&#8482;');
 349  @define('txt_registered',         '&#174;');
 350  @define('txt_copyright',          '&#169;');
 351  @define('txt_half',               '&#189;');
 352  @define('txt_quarter',            '&#188;');
 353  @define('txt_threequarters',      '&#190;');
 354  @define('txt_degrees',            '&#176;');
 355  @define('txt_plusminus',          '&#177;');
 356  @define('txt_has_unicode',        @preg_match('/\pL/u', 'a')); // Detect if Unicode is compiled into PCRE
 357  @define('txt_fn_ref_pattern',     '<sup{atts}>{marker}</sup>');
 358  @define('txt_fn_foot_pattern',    '<sup{atts}>{marker}</sup>');
 359  @define('txt_nl_ref_pattern',     '<sup{atts}>{marker}</sup>');
 360  
 361  class Textile
 362  {
 363      var $hlgn;
 364      var $vlgn;
 365      var $clas;
 366      var $lnge;
 367      var $styl;
 368      var $cspn;
 369      var $rspn;
 370      var $a;
 371      var $s;
 372      var $c;
 373      var $pnct;
 374      var $rel;
 375      var $fn;
 376  
 377      var $shelf = array();
 378      var $restricted = false;
 379      var $noimage = false;
 380      var $lite = false;
 381      var $url_schemes = array();
 382      var $glyph = array();
 383      var $hu = '';
 384      var $max_span_depth = 5;
 385  
 386      var $ver = '2.4.1';
 387      var $rev = '$Rev: 4096 $';
 388  
 389      var $doc_root;
 390  
 391      var $doctype;
 392  
 393  // -------------------------------------------------------------
 394  	function Textile( $doctype = 'xhtml' )
 395      {
 396          $doctype_whitelist = array( # All lower case please...
 397              'xhtml',
 398              'html5',
 399          );
 400          $doctype = strtolower( $doctype );
 401          if( !in_array( $doctype, $doctype_whitelist ) )
 402              $this->doctype = 'xhtml';
 403          else
 404              $this->doctype = $doctype;
 405  
 406          $this->hlgn = "(?:\<(?!>)|(?<!<)\>|\<\>|\=|[()]+(?! ))";
 407          $this->vlgn = "[\-^~]";
 408          $this->clas = "(?:\([^)\n]+\))";    # Don't allow classes/ids/languages/styles to span across newlines
 409          $this->lnge = "(?:\[[^]\n]+\])";
 410          $this->styl = "(?:\{[^}\n]+\})";
 411          $this->cspn = "(?:\\\\\d+)";
 412          $this->rspn = "(?:\/\d+)";
 413          $this->a  = "(?:{$this->hlgn}|{$this->vlgn})*";
 414          $this->s  = "(?:{$this->cspn}|{$this->rspn})*";
 415          $this->c  = "(?:{$this->clas}|{$this->styl}|{$this->lnge}|{$this->hlgn})*";
 416          $this->lc = "(?:{$this->clas}|{$this->styl}|{$this->lnge})*";
 417  
 418          $this->pnct  = '[\!"#\$%&\'()\*\+,\-\./:;<=>\?@\[\\\]\^_`{\|}\~]';
 419          $this->urlch = '[\w"$\-_.+!*\'(),";\/?:@=&%#{}|\\^~\[\]`]';
 420          $this->syms  = '¤§µ¶†‡•∗∴◊♠♣♥♦';
 421  
 422          $pnc = '[[:punct:]]';
 423  
 424          $this->restricted_url_schemes = array('http','https','ftp','mailto');
 425          $this->unrestricted_url_schemes = array('http','https','ftp','mailto','file','tel','callto','sftp');
 426  
 427          $this->btag = array('bq', 'bc', 'notextile', 'pre', 'h[1-6]', 'fn\d+', 'p', '###' );
 428  
 429          if (txt_has_unicode) {
 430              $this->regex_snippets = array(
 431                  'acr' => '\p{Lu}\p{Nd}',
 432                  'abr' => '\p{Lu}',
 433                  'nab' => '\p{Ll}',
 434                  'wrd' => '(?:\p{L}|\p{M}|\p{N}|\p{Pc})',
 435                  'mod' => 'u', # Make sure to mark the unicode patterns as such, Some servers seem to need this.
 436              );
 437          } else {
 438              $this->regex_snippets = array(
 439                  'acr' => 'A-Z0-9',
 440                  'abr' => 'A-Z',
 441                  'nab' => 'a-z',
 442                  'wrd' => '\w',
 443                  'mod' => '',
 444              );
 445          }
 446          extract( $this->regex_snippets );
 447          $this->urlch = '['.$wrd.'"$\-_.+!*\'(),";\/?:@=&%#{}|\\^~\[\]`]';
 448  
 449          $this->glyph_search = array(
 450              '/('.$wrd.'|\))\'('.$wrd.')/'.$mod,     // I'm an apostrophe
 451              '/(\s)\'(\d+'.$wrd.'?)\b(?![.]?['.$wrd.']*?\')/'.$mod,    // back in '88/the '90s but not in his '90s', '1', '1.' '10m' or '5.png'
 452              '/(\S)\'(?=\s|'.$pnc.'|<|$)/',          // single closing
 453              '/\'/',                                 // single opening
 454              '/(\S)\"(?=\s|'.$pnc.'|<|$)/',          // double closing
 455              '/"/',                                  // double opening
 456              '/\b(['.$abr.']['.$acr.']{2,})\b(?:[(]([^)]*)[)])/'.$mod,  // 3+ uppercase acronym
 457              '/(?<=\s|^|[>(;-])(['.$abr.']{3,})(['.$nab.']*)(?=\s|'.$pnc.'|<|$)(?=[^">]*?(<|$))/'.$mod,  // 3+ uppercase
 458              '/([^.]?)\.{3}/',                       // ellipsis
 459              '/(\s?)--(\s?)/',                       // em dash
 460              '/( )-( )/',                            // en dash
 461              '/(\d+)( ?)x( ?)(?=\d+)/',              // dimension sign
 462              '/(\b ?|\s|^)[([]TM[])]/i',             // trademark
 463              '/(\b ?|\s|^)[([]R[])]/i',              // registered
 464              '/(\b ?|\s|^)[([]C[])]/i',              // copyright
 465              '/[([]1\/4[])]/',                       // 1/4
 466              '/[([]1\/2[])]/',                       // 1/2
 467              '/[([]3\/4[])]/',                       // 3/4
 468              '/[([]o[])]/',                          // degrees -- that's a small 'oh'
 469              '/[([]\+\/-[])]/',                      // plus minus
 470          );
 471  
 472          $this->glyph_replace = array(
 473              '$1'.txt_apostrophe.'$2',              // I'm an apostrophe
 474              '$1'.txt_apostrophe.'$2',              // back in '88
 475              '$1'.txt_quote_single_close,           // single closing
 476              txt_quote_single_open,                 // single opening
 477              '$1'.txt_quote_double_close,           // double closing
 478              txt_quote_double_open,                 // double opening
 479              (('html5' === $this->doctype) ? '<abbr title="$2">$1</abbr>' : '<acronym title="$2">$1</acronym>'),     // 3+ uppercase acronym
 480              '<span class="caps">glyph:$1</span>$2', // 3+ uppercase
 481              '$1'.txt_ellipsis,                     // ellipsis
 482              '$1'.txt_emdash.'$2',                  // em dash
 483              '$1'.txt_endash.'$2',                  // en dash
 484              '$1$2'.txt_dimension.'$3',             // dimension sign
 485              '$1'.txt_trademark,                    // trademark
 486              '$1'.txt_registered,                   // registered
 487              '$1'.txt_copyright,                    // copyright
 488              txt_quarter,                           // 1/4
 489              txt_half,                              // 1/2
 490              txt_threequarters,                     // 3/4
 491              txt_degrees,                           // degrees
 492              txt_plusminus,                         // plus minus
 493          );
 494  
 495          if (defined('hu'))
 496              $this->hu = hu;
 497  
 498          if (defined('DIRECTORY_SEPARATOR'))
 499              $this->ds = constant('DIRECTORY_SEPARATOR');
 500          else
 501              $this->ds = '/';
 502  
 503          $this->doc_root = @$_SERVER['DOCUMENT_ROOT'];
 504          if (!$this->doc_root)
 505              $this->doc_root = @$_SERVER['PATH_TRANSLATED']; // IIS
 506  
 507          $this->doc_root = rtrim($this->doc_root, $this->ds).$this->ds;
 508      }
 509  
 510  // -------------------------------------------------------------
 511  
 512  	function TextileThis($text, $lite = '', $encode = '', $noimage = '', $strict = '', $rel = '')
 513      {
 514          $this->span_depth = 0;
 515          $this->tag_index = 1;
 516          $this->notes = $this->unreferencedNotes = $this->notelist_cache = array();
 517          $this->note_index = 1;
 518          $this->rel = ($rel) ? ' rel="'.$rel.'"' : '';
 519  
 520          $this->lite = $lite;
 521          $this->noimage = $noimage;
 522  
 523          $this->url_schemes = $this->unrestricted_url_schemes;
 524  
 525          if ($encode)
 526          {
 527              $text = $this->incomingEntities($text);
 528              $text = str_replace("x%x%", "&amp;", $text);
 529              return $text;
 530          } else {
 531              if(!$strict) {
 532                  $text = $this->cleanWhiteSpace($text);
 533              }
 534  
 535              if(!$lite) {
 536                  $text = $this->block($text);
 537                  $text = $this->placeNoteLists($text);
 538              }
 539  
 540              $text = $this->retrieve($text);
 541              $text = $this->replaceGlyphs($text);
 542              $text = $this->retrieveTags($text);
 543              $text = $this->retrieveURLs($text);
 544              $this->span_depth = 0;
 545  
 546              // just to be tidy
 547              $text = str_replace("<br />", "<br />\n", $text);
 548  
 549              return $text;
 550          }
 551      }
 552  
 553  // -------------------------------------------------------------
 554  
 555  	function TextileRestricted($text, $lite = 1, $noimage = 1, $rel = 'nofollow')
 556      {
 557          $this->restricted = true;
 558          $this->lite = $lite;
 559          $this->noimage = $noimage;
 560  
 561          $this->url_schemes = $this->restricted_url_schemes;
 562  
 563          $this->span_depth = 0;
 564          $this->tag_index = 1;
 565          $this->notes = $this->unreferencedNotes = $this->notelist_cache = array();
 566          $this->note_index = 1;
 567  
 568          $this->rel = ($rel) ? ' rel="'.$rel.'"' : '';
 569  
 570          // escape any raw html
 571          $text = $this->encode_html($text, 0);
 572          $text = $this->cleanWhiteSpace($text);
 573  
 574          if($lite) {
 575              $text = $this->blockLite($text);
 576          } else {
 577              $text = $this->block($text);
 578              $text = $this->placeNoteLists($text);
 579          }
 580  
 581          $text = $this->retrieve($text);
 582          $text = $this->replaceGlyphs($text);
 583          $text = $this->retrieveTags($text);
 584          $text = $this->retrieveURLs($text);
 585          $this->span_depth = 0;
 586  
 587          // just to be tidy
 588          $text = str_replace("<br />", "<br />\n", $text);
 589  
 590          return $text;
 591      }
 592  
 593  // -------------------------------------------------------------
 594      function cleanba( $in )
 595      {
 596          $tmp    = $in;
 597          $before = -1;
 598          $after  =  0;
 599          $max    =  3;
 600          $i      =  0;
 601          while( ($after != $before) && ($i < $max) )
 602          {
 603              $before = strlen( $tmp );
 604              $tmp    = rawurldecode($tmp);
 605              $after  = strlen( $tmp );
 606              $i++;
 607          }
 608  
 609          if( $i === $max ) # If we hit the max allowed decodes, assume the input is tainted and consume it.
 610              $out = '';
 611          else
 612              $out = strtr( $tmp, array(
 613                  '"'=>'',
 614                  "'"=>'',
 615                  '='=>'',
 616              ));
 617          return $out;
 618      }
 619  
 620  // -------------------------------------------------------------
 621  	function pba($in, $element = "", $include_id = 1, $autoclass = '') // "parse block attributes"
 622      {
 623          $style = '';
 624          $class = '';
 625          $lang = '';
 626          $colspan = '';
 627          $rowspan = '';
 628          $span = '';
 629          $width = '';
 630          $id = '';
 631          $atts = '';
 632          $align = '';
 633  
 634          $matched = $in;
 635          if ($element == 'td') {
 636              if (preg_match("/\\\\(\d+)/", $matched, $csp)) $colspan = $csp[1];
 637              if (preg_match("/\/(\d+)/", $matched, $rsp)) $rowspan = $rsp[1];
 638          }
 639  
 640          if ($element == 'td' or $element == 'tr') {
 641              if (preg_match("/($this->vlgn)/", $matched, $vert))
 642                  $style[] = "vertical-align:" . $this->vAlign($vert[1]);
 643          }
 644  
 645          if (preg_match("/\{([^}]*)\}/", $matched, $sty)) {
 646              $style[] = rtrim($sty[1], ';');
 647              $matched = str_replace($sty[0], '', $matched);
 648          }
 649  
 650          if (preg_match("/\[([^]]+)\]/U", $matched, $lng)) {
 651              $matched = str_replace($lng[0], '', $matched);    # Consume entire lang block -- valid or invalid...
 652              if (preg_match("/\[([a-zA-Z]{2}(?:[\-\_][a-zA-Z]{2})?)\]/U", $lng[0], $lng)) {
 653                  $lang = $lng[1];
 654              }
 655          }
 656  
 657          if (preg_match("/\(([^()]+)\)/U", $matched, $cls)) {
 658              $matched = str_replace($cls[0], '', $matched);    # Consume entire class block -- valid or invalid...
 659              # Only allow a restricted subset of the CSS standard characters for classes/ids. No encoding markers allowed...
 660              if (preg_match("/\(([-a-zA-Z 0-9_\.\:\#]+)\)/U", $cls[0], $cls)) {
 661                  $hashpos = strpos( $cls[1], '#' );
 662                  # If a textile class block attribute was found with a '#' in it
 663                  # split it into the css class and css id...
 664                  if( false !== $hashpos ) {
 665                      if (preg_match("/#([-a-zA-Z0-9_\.\:]*)$/", substr( $cls[1], $hashpos ), $ids))
 666                          $id = $ids[1];
 667  
 668                      if (preg_match("/^([-a-zA-Z 0-9_]*)/", substr( $cls[1], 0, $hashpos ), $ids))
 669                          $class = $ids[1];
 670                  }
 671                  else {
 672                      if (preg_match("/^([-a-zA-Z 0-9_]*)$/", $cls[1], $ids))
 673                          $class = $ids[1];
 674                  }
 675              }
 676          }
 677  
 678          if (preg_match("/([(]+)/", $matched, $pl)) {
 679              $style[] = "padding-left:" . strlen($pl[1]) . "em";
 680              $matched = str_replace($pl[0], '', $matched);
 681          }
 682  
 683          if (preg_match("/([)]+)/", $matched, $pr)) {
 684              $style[] = "padding-right:" . strlen($pr[1]) . "em";
 685              $matched = str_replace($pr[0], '', $matched);
 686          }
 687  
 688          if (preg_match("/($this->hlgn)/", $matched, $horiz))
 689              $style[] = "text-align:" . $this->hAlign($horiz[1]);
 690  
 691          if ($element == 'col') {
 692              if (preg_match("/(?:\\\\(\d+))?\s*(\d+)?/", $matched, $csp)) {
 693                  $span = isset($csp[1]) ? $csp[1] : '';
 694                  $width = isset($csp[2]) ? $csp[2] : '';
 695              }
 696          }
 697  
 698          if ($this->restricted) {
 699              $class = trim( $autoclass );
 700              return join( '', array(
 701                  ($lang)  ? ' lang="'  . $this->cleanba($lang)  . '"': '',
 702                  ($class) ? ' class="' . $this->cleanba($class) . '"': '',
 703              ));
 704          }
 705          else
 706              $class = trim( $class . ' ' . $autoclass );
 707  
 708          $o = '';
 709          if( $style ) {
 710              foreach($style as $s) {
 711                  $parts = explode(';', $s);
 712                  foreach( $parts as $p ) {
 713                      $p = trim($p, '; ');
 714                      if( !empty( $p ) )
 715                          $o .= $p.'; ';
 716                  }
 717              }
 718              $style = trim( strtr($o, array("\n"=>'',';;'=>';')) );
 719          }
 720  
 721          return join('',array(
 722              ($style)   ? ' style="'   . $this->cleanba($style)    .'"' : '',
 723              ($class)   ? ' class="'   . $this->cleanba($class)    .'"' : '',
 724              ($lang)    ? ' lang="'    . $this->cleanba($lang)     .'"' : '',
 725              ($id and $include_id) ? ' id="' . $this->cleanba($id) .'"' : '',
 726              ($colspan) ? ' colspan="' . $this->cleanba($colspan)  .'"' : '',
 727              ($rowspan) ? ' rowspan="' . $this->cleanba($rowspan)  .'"' : '',
 728              ($span)    ? ' span="'    . $this->cleanba($span)     .'"' : '',
 729              ($width)   ? ' width="'   . $this->cleanba($width)    .'"' : '',
 730          ));
 731      }
 732  
 733  // -------------------------------------------------------------
 734  	function hasRawText($text)
 735      {
 736          // checks whether the text has text not already enclosed by a block tag
 737          $r = trim(preg_replace('@<(p|blockquote|div|form|table|ul|ol|dl|pre|h\d)[^>]*?'.chr(62).'.*</\1>@s', '', trim($text)));
 738          $r = trim(preg_replace('@<(hr|br)[^>]*?/>@', '', $r));
 739          return '' != $r;
 740      }
 741  
 742  // -------------------------------------------------------------
 743  	function table($text)
 744      {
 745          $text = $text . "\n\n";
 746          return preg_replace_callback("/^(?:table(_?{$this->s}{$this->a}{$this->c})\.(.*)?\n)?^({$this->a}{$this->c}\.? ?\|.*\|)[\s]*\n\n/smU",
 747               array(&$this, "fTable"), $text);
 748      }
 749  
 750  // -------------------------------------------------------------
 751  	function fTable($matches)
 752      {
 753          $tatts = $this->pba($matches[1], 'table');
 754  
 755          $sum = trim($matches[2]) ? ' summary="'.htmlspecialchars(trim($matches[2])).'"' : '';
 756          $cap = '';
 757          $colgrp = $last_rgrp = '';
 758          $c_row = 1;
 759          foreach(preg_split("/\|\s*?$/m", $matches[3], -1, PREG_SPLIT_NO_EMPTY) as $row) {
 760  
 761              $row = ltrim($row);
 762  
 763              // Caption -- can only occur on row 1, otherwise treat '|=. foo |...' as a normal center-aligned cell.
 764              if ( ($c_row <= 1) && preg_match("/^\|\=($this->s$this->a$this->c)\. ([^\n]*)(.*)/s", ltrim($row), $cmtch)) {
 765                  $capts = $this->pba($cmtch[1]);
 766                  $cap = "\t<caption".$capts.">".trim($cmtch[2])."</caption>\n";
 767                  $row = ltrim($cmtch[3]);
 768                  if( empty($row) )
 769                      continue;
 770              }
 771              $c_row += 1;
 772  
 773              // Colgroup
 774              if (preg_match("/^\|:($this->s$this->a$this->c\. .*)/m", ltrim($row), $gmtch)) {
 775                  $nl = strpos($row,"\n");    # Is this colgroup def missing a closing pipe? If so, there will be a newline in the middle of $row somewhere.
 776                  $idx=0;
 777                  foreach (explode('|', str_replace('.', '', $gmtch[1])) as $col) {
 778                      $gatts = $this->pba(trim($col), 'col');
 779                      $colgrp .= "\t<col".(($idx==0) ? "group".$gatts.">" : $gatts." />")."\n";
 780                      $idx++;
 781                  }
 782                  $colgrp .= "\t</colgroup>\n";
 783  
 784                  if($nl === false) {
 785                      continue;
 786                  }
 787                  else {
 788                      $row = ltrim(substr( $row, $nl ));        # Recover from our missing pipe and process the rest of the line...
 789                  }
 790              }
 791  
 792              preg_match("/(:?^\|($this->vlgn)($this->s$this->a$this->c)\.\s*$\n)?^(.*)/sm", ltrim($row), $grpmatch);
 793  
 794              // Row group
 795              $rgrp = isset($grpmatch[2]) ? (($grpmatch[2] == '^') ? 'head' : ( ($grpmatch[2] == '~') ? 'foot' : (($grpmatch[2] == '-') ? 'body' : '' ) ) ) : '';
 796              $rgrpatts = isset($grpmatch[3]) ? $this->pba($grpmatch[3]) : '';
 797              $row = $grpmatch[4];
 798  
 799              if (preg_match("/^($this->a$this->c\. )(.*)/m", ltrim($row), $rmtch)) {
 800                  $ratts = $this->pba($rmtch[1], 'tr');
 801                  $row = $rmtch[2];
 802              } else $ratts = '';
 803  
 804              $cells = array();
 805              $cellctr = 0;
 806              foreach(explode("|", $row) as $cell) {
 807                  $ctyp = "d";
 808                  if (preg_match("/^_/", $cell)) $ctyp = "h";
 809                  if (preg_match("/^(_?$this->s$this->a$this->c\. )(.*)/", $cell, $cmtch)) {
 810                      $catts = $this->pba($cmtch[1], 'td');
 811                      $cell = $cmtch[2];
 812                  } else $catts = '';
 813  
 814                  if (!$this->lite) {
 815                      $cell = $this->redcloth_lists($cell);
 816                      $cell = $this->lists($cell);
 817                  }
 818  
 819                  if ($cellctr>0) // Ignore first 'cell': it precedes the opening pipe
 820                      $cells[] = $this->doTagBr("t$ctyp", "\t\t\t<t$ctyp$catts>$cell</t$ctyp>");
 821  
 822                  $cellctr++;
 823              }
 824              $grp = (($rgrp && $last_rgrp) ? "\t</t".$last_rgrp.">\n" : '') . (($rgrp) ? "\t<t".$rgrp.$rgrpatts.">\n" : '');
 825              $last_rgrp = ($rgrp) ? $rgrp : $last_rgrp;
 826              $rows[] = $grp."\t\t<tr$ratts>\n" . join("\n", $cells) . ($cells ? "\n" : "") . "\t\t</tr>";
 827              unset($cells, $catts);
 828          }
 829  
 830          return "\t<table{$tatts}{$sum}>\n" .$cap. $colgrp. join("\n", $rows) . "\n".(($last_rgrp) ? "\t</t".$last_rgrp.">\n" : '')."\t</table>\n\n";
 831      }
 832  
 833  // -------------------------------------------------------------
 834  	function redcloth_lists($text)
 835      {
 836          return preg_replace_callback("/^([-]+$this->lc[ .].*:=.*)$(?![^-])/smU", array(&$this, "fRCList"), $text);
 837      }
 838  
 839  // -------------------------------------------------------------
 840  	function fRCList($m)
 841      {
 842          $out = array();
 843          $text = preg_split('/\n(?=[-])/m', $m[0]);
 844          foreach($text as $nr => $line) {
 845              if (preg_match("/^[-]+($this->lc)[ .](.*)$/s", $line, $m)) {
 846                  list(, $atts, $content) = $m;
 847                  $content = trim($content);
 848                  $atts = $this->pba($atts);
 849  
 850                  preg_match( "/^(.*?)[\s]*:=(.*?)[\s]*(=:|:=)?[\s]*$/s", $content, $xm );
 851                  list( , $term, $def, ) = $xm;
 852                  $term = trim( $term );
 853                  $def  = trim( $def, ' ' );
 854  
 855                  if( empty( $out ) ) {
 856                      if(''==$def)
 857                          $out[] = "<dl$atts>";
 858                      else
 859                          $out[] = '<dl>';
 860                  }
 861  
 862                  if( '' != $def && '' != $term )
 863                  {
 864                      $pos = strpos( $def, "\n" );
 865                      $def = str_replace( "\n", "<br />", trim($def) );
 866                      if( 0 === $pos )
 867                          $def  = '<p>' . $def . '</p>';
 868  
 869                      $term = $this->graf($term);
 870                      $def  = $this->graf($def);
 871  
 872                      $out[] = "\t<dt$atts>$term</dt>";
 873                      $out[] = "\t<dd>$def</dd>";
 874                  }
 875              }
 876          }
 877          $out[] = '</dl>';
 878          return implode("\n", $out);
 879      }
 880  
 881  
 882  // -------------------------------------------------------------
 883  	function lists($text)
 884      {
 885          return preg_replace_callback("/^((?:[*;:]+|[*;:#]*#(?:_|\d+)?)$this->lc[ .].*)$(?![^#*;:])/smU", array(&$this, "fList"), $text);
 886      }
 887  
 888  // -------------------------------------------------------------
 889  	function fList($m)
 890      {
 891          $text = preg_split('/\n(?=[*#;:])/m', $m[0]);
 892          $pt = '';
 893          foreach($text as $nr => $line) {
 894              $nextline = isset($text[$nr+1]) ? $text[$nr+1] : false;
 895              if (preg_match("/^([#*;:]+)(_|\d+)?($this->lc)[ .](.*)$/s", $line, $m)) {
 896                  list(, $tl, $st, $atts, $content) = $m;
 897                  $content = trim($content);
 898                  $nl = '';
 899                  $ltype = $this->lT($tl);
 900                  $litem = (strpos($tl, ';') !== false) ? 'dt' : ((strpos($tl, ':') !== false) ? 'dd' : 'li');
 901                  $showitem = (strlen($content) > 0);
 902  
 903                  if( 'o' === $ltype ) {                    // handle list continuation/start attribute on ordered lists...
 904                      if( !isset($this->olstarts[$tl]) )
 905                          $this->olstarts[$tl] = 1;
 906  
 907                      if( strlen($tl) > strlen($pt) ) {            // first line of this level of ol -- has a start attribute?
 908                          if( '' == $st )
 909                              $this->olstarts[$tl] = 1;            // no => reset count to 1.
 910                          elseif( '_' !== $st )
 911                              $this->olstarts[$tl] = (int)$st;    // yes, and numeric => reset to given.
 912                                                                  // TRICKY: the '_' continuation marker just means
 913                                                                  // output the count so don't need to do anything
 914                                                                  // here.
 915                      }
 916  
 917                      if( (strlen($tl) > strlen($pt)) && '' !== $st)        // output the start attribute if needed...
 918                          $st = ' start="' . $this->olstarts[$tl] . '"';
 919  
 920                      if( $showitem )                             // TRICKY: Only increment the count for list items; not when a list definition line is encountered.
 921                          $this->olstarts[$tl] += 1;
 922                  }
 923  
 924                  if (preg_match("/^([#*;:]+)(_|[\d]+)?($this->lc)[ .].*/", $nextline, $nm))
 925                      $nl = $nm[1];
 926  
 927                  if ((strpos($pt, ';') !== false) && (strpos($tl, ':') !== false)) {
 928                      $lists[$tl] = 2; // We're already in a <dl> so flag not to start another
 929                  }
 930  
 931                  $atts = $this->pba($atts);
 932                  if (!isset($lists[$tl])) {
 933                      $lists[$tl] = 1;
 934                      $line = "\t<" . $ltype . "l$atts$st>" . (($showitem) ? "\n\t\t<$litem>" . $content : '');
 935                  } else {
 936                      $line = ($showitem) ? "\t\t<$litem$atts>" . $content : '';
 937                  }
 938  
 939                  if((strlen($nl) <= strlen($tl))) $line .= (($showitem) ? "</$litem>" : '');
 940                  foreach(array_reverse($lists) as $k => $v) {
 941                      if(strlen($k) > strlen($nl)) {
 942                          $line .= ($v==2) ? '' : "\n\t</" . $this->lT($k) . "l>";
 943                          if((strlen($k) > 1) && ($v != 2))
 944                              $line .= "</".$litem.">";
 945                          unset($lists[$k]);
 946                      }
 947                  }
 948                  $pt = $tl; // Remember the current Textile tag
 949              }
 950              else {
 951                  $line .= "\n";
 952              }
 953              $out[] = $line;
 954          }
 955          return $this->doTagBr($litem, join("\n", $out));
 956      }
 957  
 958  // -------------------------------------------------------------
 959      function lT($in)
 960      {
 961          return preg_match("/^#+/", $in) ? 'o' : ((preg_match("/^\*+/", $in)) ? 'u' : 'd');
 962      }
 963  
 964  // -------------------------------------------------------------
 965  	function doTagBr($tag, $in)
 966      {
 967          return preg_replace_callback('@<('.preg_quote($tag).')([^>]*?)>(.*)(</\1>)@s', array(&$this, 'fBr'), $in);
 968      }
 969  
 970  // -------------------------------------------------------------
 971  	function doPBr($in)
 972      {
 973          return preg_replace_callback('@<(p)([^>]*?)>(.*)(</\1>)@s', array(&$this, 'fPBr'), $in);
 974      }
 975  
 976  // -------------------------------------------------------------
 977  	function fPBr($m)
 978      {
 979          # Less restrictive version of fBr() ... used only in paragraphs where the next
 980          # row may start with a smiley or perhaps something like '#8 bolt...' or '*** stars...'
 981          $content = preg_replace("@(.+)(?<!<br>|<br />)\n(?![\s|])@", '$1<br />', $m[3]);
 982          return '<'.$m[1].$m[2].'>'.$content.$m[4];
 983      }
 984  
 985  // -------------------------------------------------------------
 986  	function fBr($m)
 987      {
 988          $content = preg_replace("@(.+)(?<!<br>|<br />)\n(?![#*;:\s|])@", '$1<br />', $m[3]);
 989          return '<'.$m[1].$m[2].'>'.$content.$m[4];
 990      }
 991  
 992  // -------------------------------------------------------------
 993  	function block($text)
 994      {
 995          $find = $this->btag;
 996          $tre = join('|', $find);
 997  
 998          $text = explode("\n\n", $text);
 999  
1000          $tag = 'p';
1001          $atts = $cite = $graf = $ext = '';
1002          $eat = false;
1003  
1004          $out = array();
1005  
1006          foreach($text as $line) {
1007              $anon = 0;
1008              if (preg_match("/^($tre)($this->a$this->c)\.(\.?)(?::(\S+))? (.*)$/s", $line, $m)) {
1009                  // last block was extended, so close it
1010                  if ($ext)
1011                      $out[count($out)-1] .= $c1;
1012                  // new block
1013                  list(,$tag,$atts,$ext,$cite,$graf) = $m;
1014                  list($o1, $o2, $content, $c2, $c1, $eat) = $this->fBlock(array(0,$tag,$atts,$ext,$cite,$graf));
1015  
1016                  // leave off c1 if this block is extended, we'll close it at the start of the next block
1017                  if ($ext)
1018                      $line = $o1.$o2.$content.$c2;
1019                  else
1020                      $line = $o1.$o2.$content.$c2.$c1;
1021              }
1022              else {
1023                  // anonymous block
1024                  $anon = 1;
1025                  if ($ext or !preg_match('/^ /', $line)) {
1026                      list($o1, $o2, $content, $c2, $c1, $eat) = $this->fBlock(array(0,$tag,$atts,$ext,$cite,$line));
1027                      // skip $o1/$c1 because this is part of a continuing extended block
1028                      if ($tag == 'p' and !$this->hasRawText($content)) {
1029                          $line = $content;
1030                      }
1031                      else {
1032                          $line = $o2.$content.$c2;
1033                      }
1034                  }
1035                  else {
1036                      $line = $this->graf($line);
1037                  }
1038              }
1039  
1040              $line = $this->doPBr($line);
1041              $line = preg_replace('/<br>/', '<br />', $line);
1042  
1043              if ($ext and $anon)
1044                  $out[count($out)-1] .= "\n".$line;
1045              elseif(!$eat)
1046                  $out[] = $line;
1047  
1048              if (!$ext) {
1049                  $tag = 'p';
1050                  $atts = '';
1051                  $cite = '';
1052                  $graf = '';
1053                  $eat = false;
1054              }
1055          }
1056          if ($ext) $out[count($out)-1] .= $c1;
1057          return join("\n\n", $out);
1058      }
1059  
1060  // -------------------------------------------------------------
1061  	function formatFootnote( $marker, $atts='', $anchor=true )
1062      {
1063          $pattern = ($anchor) ? txt_fn_foot_pattern : txt_fn_ref_pattern;
1064          return $this->replaceMarkers( $pattern, array( 'atts' => $atts, 'marker' => $marker ) );
1065      }
1066  
1067  // -------------------------------------------------------------
1068  	function replaceMarkers( $text, $replacements )
1069      {
1070          if( !empty( $replacements ) )
1071              foreach( $replacements as $k => $r )
1072                  $text = str_replace( '{'.$k.'}', $r, $text );
1073          return $text;
1074      }
1075  
1076  // -------------------------------------------------------------
1077  	function fBlock($m)
1078      {
1079          extract($this->regex_snippets);
1080          list(, $tag, $att, $ext, $cite, $content) = $m;
1081          $atts = $this->pba($att);
1082  
1083          $o1 = $o2 = $c2 = $c1 = '';
1084          $eat = false;
1085  
1086          if( $tag === 'p' ) {
1087              # Is this an anonymous block with a note definition?
1088              $notedef = preg_replace_callback("/
1089                      ^note\#               #  start of note def marker
1090                      ([^%<*!@#^([{ \s.]+)  # !label
1091                      ([*!^]?)              # !link
1092                      ({$this->c})          # !att
1093                      \.?                   #  optional period.
1094                      [\s]+                 #  whitespace ends def marker
1095                      (.*)$                 # !content
1096                  /x$mod", array(&$this, "fParseNoteDefs"), $content);
1097  
1098              if( '' === $notedef ) # It will be empty if the regex matched and ate it.
1099                  return array($o1, $o2, $notedef, $c2, $c1, true);
1100              }
1101  
1102          if (preg_match("/fn(\d+)/", $tag, $fns)) {
1103              $tag = 'p';
1104              $fnid = empty($this->fn[$fns[1]]) ? $fns[1] : $this->fn[$fns[1]];
1105  
1106              # If there is an author-specified ID goes on the wrapper & the auto-id gets pushed to the <sup>
1107              $supp_id = '';
1108              if (strpos($atts, ' id=') === false)
1109                  $atts .= ' id="fn' . $fnid . '"';
1110              else
1111                  $supp_id = ' id="fn' . $fnid . '"';
1112  
1113              if (strpos($atts, 'class=') === false)
1114                  $atts .= ' class="footnote"';
1115  
1116              $sup = (strpos($att, '^') === false) ? $this->formatFootnote($fns[1], $supp_id) : $this->formatFootnote('<a href="#fnrev' . $fnid . '">'.$fns[1] .'</a>', $supp_id);
1117  
1118              $content = $sup . ' ' . $content;
1119          }
1120  
1121          if ($tag == "bq") {
1122              $cite = $this->shelveURL($cite);
1123              $cite = ($cite != '') ? ' cite="' . $cite . '"' : '';
1124              $o1 = "\t<blockquote$cite$atts>\n";
1125              $o2 = "\t\t<p".$this->pba($att, '', 0).">";
1126              $c2 = "</p>";
1127              $c1 = "\n\t</blockquote>";
1128          }
1129          elseif ($tag == 'bc') {
1130              $o1 = "<pre$atts>";
1131              $o2 = "<code>";
1132              $c2 = "</code>";
1133              $c1 = "</pre>";
1134              $content = $this->shelve($this->r_encode_html(rtrim($content, "\n")."\n"));
1135          }
1136          elseif ($tag == 'notextile') {
1137              $content = $this->shelve($content);
1138              $o1 = $o2 = '';
1139              $c1 = $c2 = '';
1140          }
1141          elseif ($tag == 'pre') {
1142              $content = $this->shelve($this->r_encode_html(rtrim($content, "\n")."\n"));
1143              $o1 = "<pre$atts>";
1144              $o2 = $c2 = '';
1145              $c1 = "</pre>";
1146          }
1147          elseif ($tag == '###') {
1148              $eat = true;
1149          }
1150          else {
1151              $o2 = "\t<$tag$atts>";
1152              $c2 = "</$tag>";
1153          }
1154  
1155          $content = (!$eat) ? $this->graf($content) : '';
1156  
1157          return array($o1, $o2, $content, $c2, $c1, $eat);
1158      }
1159  
1160  // -------------------------------------------------------------
1161  	function fParseHTMLComments($m)
1162      {
1163          list( , $content ) = $m;
1164          if( $this->restricted )
1165              $content = $this->shelve($this->r_encode_html($content));
1166          else
1167              $content = $this->shelve($content);
1168          return "<!--$content-->";
1169      }
1170  
1171  
1172  	function getHTMLComments($text)
1173      {
1174          $text = preg_replace_callback("/
1175              \<!--    #  start
1176              (.*?)    # !content
1177              -->      #  end
1178          /sx", array(&$this, "fParseHTMLComments"), $text);
1179          return $text;
1180      }
1181  
1182  // -------------------------------------------------------------
1183  	function graf($text)
1184      {
1185          // handle normal paragraph text
1186          if (!$this->lite) {
1187              $text = $this->noTextile($text);
1188              $text = $this->code($text);
1189          }
1190  
1191          $text = $this->getHTMLComments($text);
1192          $text = $this->getRefs($text);
1193          $text = $this->links($text);
1194          if (!$this->noimage)
1195              $text = $this->image($text);
1196  
1197          if (!$this->lite) {
1198              $text = $this->table($text);
1199              $text = $this->redcloth_lists($text);
1200              $text = $this->lists($text);
1201          }
1202  
1203          $text = $this->span($text);
1204          $text = $this->footnoteRef($text);
1205          $text = $this->noteRef($text);
1206          $text = $this->glyphs($text);
1207  
1208          return rtrim($text, "\n");
1209      }
1210  
1211  // -------------------------------------------------------------
1212  	function span($text)
1213      {
1214          $qtags = array('\*\*','\*','\?\?','-','__','_','%','\+','~','\^');
1215          $pnct = ".,\"'?!;:‹›«»„“”‚‘’";
1216          $this->span_depth++;
1217  
1218          if( $this->span_depth <= $this->max_span_depth )
1219          {
1220              foreach($qtags as $f)
1221              {
1222                  $text = preg_replace_callback("/
1223                      (^|(?<=[\s>$pnct\(])|[{[])            # pre
1224                      ($f)(?!$f)                            # tag
1225                      ({$this->c})                          # atts
1226                      (?::(\S+))?                           # cite
1227                      ([^\s$f]+|\S.*?[^\s$f\n])             # content
1228                      ([$pnct]*)                            # end
1229                      $f
1230                      ($|[\[\]}<]|(?=[$pnct]{1,2}|\s|\)))  # tail
1231                  /x".$this->regex_snippets['mod'], array(&$this, "fSpan"), $text);
1232              }
1233          }
1234          $this->span_depth--;
1235          return $text;
1236      }
1237  
1238  // -------------------------------------------------------------
1239  	function fSpan($m)
1240      {
1241          $qtags = array(
1242              '*'  => 'strong',
1243              '**' => 'b',
1244              '??' => 'cite',
1245              '_'  => 'em',
1246              '__' => 'i',
1247              '-'  => 'del',
1248              '%'  => 'span',
1249              '+'  => 'ins',
1250              '~'  => 'sub',
1251              '^'  => 'sup',
1252          );
1253  
1254          list(, $pre, $tag, $atts, $cite, $content, $end, $tail) = $m;
1255  
1256          $tag = $qtags[$tag];
1257          $atts = $this->pba($atts);
1258          $atts .= ($cite != '') ? 'cite="' . $cite . '"' : '';
1259  
1260          $content = $this->span($content);
1261  
1262          $opentag = '<'.$tag.$atts.'>';
1263          $closetag = '</'.$tag.'>';
1264          $tags = $this->storeTags($opentag, $closetag);
1265          $out = "{$tags['open']}{$content}{$end}{$tags['close']}";
1266  
1267          if (($pre and !$tail) or ($tail and !$pre))
1268              $out = $pre.$out.$tail;
1269  
1270          return $out;
1271      }
1272  
1273  // -------------------------------------------------------------
1274  	function storeTags($opentag,$closetag='')
1275      {
1276          $key = ($this->tag_index++);
1277  
1278          $key = str_pad( (string)$key, 10, '0', STR_PAD_LEFT ); # $key must be of fixed length to allow proper matching in retrieveTags
1279          $this->tagCache[$key] = array('open'=>$opentag, 'close'=>$closetag);
1280          $tags = array(
1281              'open'  => "textileopentag{$key} ",
1282              'close' => " textileclosetag{$key}",
1283          );
1284          return $tags;
1285      }
1286  
1287  // -------------------------------------------------------------
1288  	function retrieveTags($text)
1289      {
1290          $text = preg_replace_callback('/textileopentag([\d]{10}) /' , array(&$this, 'fRetrieveOpenTags'),  $text);
1291          $text = preg_replace_callback('/ textileclosetag([\d]{10})/', array(&$this, 'fRetrieveCloseTags'), $text);
1292          return $text;
1293      }
1294  
1295  // -------------------------------------------------------------
1296  	function fRetrieveOpenTags($m)
1297      {
1298          list(, $key ) = $m;
1299          return $this->tagCache[$key]['open'];
1300      }
1301  
1302  // -------------------------------------------------------------
1303  	function fRetrieveCloseTags($m)
1304      {
1305          list(, $key ) = $m;
1306          return $this->tagCache[$key]['close'];
1307      }
1308  
1309  // -------------------------------------------------------------
1310  	function placeNoteLists($text)
1311      {
1312          extract($this->regex_snippets);
1313  
1314          # Sequence all referenced definitions...
1315          if( !empty($this->notes) ) {
1316              $o = array();
1317              foreach( $this->notes as $label=>$info ) {
1318                  $i = @$info['seq'];
1319                  if( !empty($i) ) {
1320                      $info['seq'] = $label;
1321                      $o[$i] = $info;
1322                  } else {
1323                      $this->unreferencedNotes[] = $info;    # unreferenced definitions go here for possible future use.
1324                  }
1325              }
1326              if( !empty($o) ) ksort($o);
1327              $this->notes = $o;
1328          }
1329  
1330          # Replace list markers...
1331          $text = preg_replace_callback("@<p>notelist({$this->c})(?:\:([$wrd|{$this->syms}]))?([\^!]?)(\+?)\.?[\s]*</p>@U$mod", array(&$this, "fNoteLists"), $text );
1332  
1333          return $text;
1334      }
1335  
1336  // -------------------------------------------------------------
1337  	function fNoteLists($m)
1338      {
1339          list(, $att, $start_char, $g_links, $extras) = $m;
1340          if( !$start_char ) $start_char = 'a';
1341          $index = $g_links.$extras.$start_char;
1342  
1343          if( empty($this->notelist_cache[$index]) ) { # If not in cache, build the entry...
1344              $o = array();
1345  
1346              if( !empty($this->notes)) {
1347                  foreach($this->notes as $seq=>$info) {
1348                      $links = $this->makeBackrefLink($info, $g_links, $start_char );
1349                      $atts = '';
1350                      if( !empty($info['def'])) {
1351                          $id = $info['id'];
1352                          extract($info['def']);
1353                          $o[] = "\t".'<li'.$atts.'>'.$links.'<span id="note'.$id.'"> </span>'.$content.'</li>';
1354                      } else {
1355                          $o[] = "\t".'<li'.$atts.'>'.$links.' Undefined Note [#'.$info['seq'].'].</li>';
1356                      }
1357                  }
1358              }
1359              if( '+' == $extras && !empty($this->unreferencedNotes) ) {
1360                  foreach($this->unreferencedNotes as $seq=>$info) {
1361                      if( !empty($info['def'])) {
1362                          extract($info['def']);
1363                          $o[] = "\t".'<li'.$atts.'>'.$content.'</li>';
1364                      }
1365                  }
1366              }
1367  
1368              $this->notelist_cache[$index] = join("\n",$o);
1369          }
1370  
1371          $_ = ($this->notelist_cache[$index]) ? $this->notelist_cache[$index] : '';
1372  
1373          if( !empty($_) ) {
1374              $list_atts = $this->pba($att);
1375              $_ = "<ol$list_atts>\n$_\n</ol>";
1376          }
1377  
1378          return $_;
1379      }
1380  
1381  // -------------------------------------------------------------
1382  	function makeBackrefLink( &$info, $g_links, $i )
1383      {
1384          $atts = $content = $id = $link = '';
1385          @extract( $info['def'] );
1386          $backlink_type = ($link) ? $link : $g_links;
1387          $allow_inc = (false === strpos( $this->syms, $i ) );
1388  
1389          $i_ = strtr( $this->encode_high($i) , array('&'=>'', ';'=>'', '#'=>''));
1390          $decode = (strlen($i) !== strlen($i_));
1391  
1392          if( $backlink_type === '!' )
1393              return '';
1394          elseif( $backlink_type === '^' )
1395              return '<sup><a href="#noteref'.$info['refids'][0].'">'.$i.'</a></sup>';
1396          else {
1397              $_ = array();
1398              foreach( $info['refids'] as $id ) {
1399                  $_[] = '<sup><a href="#noteref'.$id.'">'. ( ($decode) ? $this->decode_high('&#'.$i_.';') : $i_ ) .'</a></sup>';
1400                  if( $allow_inc )
1401                      $i_++;
1402              }
1403              $_ = join( ' ', $_ );
1404              return $_;
1405          }
1406      }
1407  
1408  
1409  // -------------------------------------------------------------
1410  	function fParseNoteDefs($m)
1411      {
1412          list(, $label, $link, $att, $content) = $m;
1413          # Assign an id if the note reference parse hasn't found the label yet.
1414          $id = @$this->notes[$label]['id'];
1415          if( !$id )
1416              $this->notes[$label]['id'] = uniqid(rand());
1417  
1418          if( empty($this->notes[$label]['def']) ) # Ignores subsequent defs using the same label
1419          {
1420              $this->notes[$label]['def'] = array(
1421                  'atts'    => $this->pba($att),
1422                  'content' => $this->graf($content),
1423                  'link'    => $link,
1424              );
1425          }
1426          return '';
1427      }
1428  
1429  // -------------------------------------------------------------
1430  	function noteRef($text)
1431      {
1432          $text = preg_replace_callback("/
1433              \[                   #  start
1434              ({$this->c})         # !atts
1435              \#
1436              ([^\]!]+?)           # !label
1437              ([!]?)               # !nolink
1438              \]
1439          /Ux", array(&$this, "fParseNoteRefs"), $text);
1440          return $text;
1441      }
1442  
1443  // -------------------------------------------------------------
1444  	function fParseNoteRefs($m)
1445      {
1446          #   By the time this function is called, all the defs will have been processed
1447          # into the notes array. So now we can resolve the link numbers in the order
1448          # we process the refs...
1449  
1450          list(, $atts, $label, $nolink) = $m;
1451          $atts = $this->pba($atts);
1452          $nolink = ($nolink === '!');
1453  
1454          # Assign a sequence number to this reference if there isn't one already...
1455          $num = @$this->notes[$label]['seq'];
1456          if( !$num )
1457              $num = $this->notes[$label]['seq'] = ($this->note_index++);
1458  
1459          # Make our anchor point & stash it for possible use in backlinks when the
1460          # note list is generated later...
1461          $this->notes[$label]['refids'][] = $refid = uniqid(rand());
1462  
1463          # If we are referencing a note that hasn't had the definition parsed yet, then assign it an ID...
1464          $id = @$this->notes[$label]['id'];
1465          if( !$id )
1466              $id = $this->notes[$label]['id'] = uniqid(rand());
1467  
1468          # Build the link (if any)...
1469          $_ = '<span id="noteref'.$refid.'">'.$num.'</span>';
1470          if( !$nolink )
1471              $_ = '<a href="#note'.$id.'">'.$_.'</a>';
1472  
1473          # Build the reference...
1474          $_ = $this->replaceMarkers( txt_nl_ref_pattern, array( 'atts' => $atts, 'marker' => $_ ) );
1475  
1476          return $_;
1477      }
1478  
1479  // -------------------------------------------------------------
1480      /**
1481       * Parse URI
1482       *
1483       * Regex taken from the RFC at http://tools.ietf.org/html/rfc3986#appendix-B
1484       **/
1485  	function parseURI( $uri, &$m )
1486      {
1487          $r = "@^((?P<scheme>[^:/?#]+):)?(//(?P<authority>[^/?#]*))?(?P<path>[^?#]*)(\?(?P<query>[^#]*))?(#(?P<fragment>.*))?@";
1488          #       12                      3  4                       5               6  7                 8 9
1489          #
1490          #    scheme    = $2
1491          #    authority = $4
1492          #     path      = $5
1493          #    query     = $7
1494          #    fragment  = $9
1495  
1496          $ok = preg_match( $r, $uri, $m );
1497          return $ok;
1498      }
1499  
1500  	function addPart( &$mask, $name, &$parts ) {
1501          return (in_array($name, $mask) && isset( $parts[$name]) && '' !== $parts[$name]);
1502      }
1503  
1504  
1505  // -------------------------------------------------------------
1506      /**
1507       * Rebuild a URI from parsed parts and a mask.
1508       *
1509       * Algorithm based on example from http://tools.ietf.org/html/rfc3986#section-5.3
1510       **/
1511  	function rebuildURI( $parts, $mask='scheme,authority,path,query,fragment', $encode=true )
1512      {
1513          $mask = explode( ',', $mask );
1514          $out  = '';
1515  
1516          if( $this->addPart( $mask, 'scheme', $parts ) ) {
1517              $out .= $parts['scheme'] . ':';
1518          }
1519  
1520          if( $this->addPart( $mask, 'authority', $parts) ) {
1521              $out .= '//' . $parts['authority'];
1522          }
1523  
1524          if( $this->addPart( $mask, 'path', $parts ) ) {
1525              if( !$encode )
1526                  $out .= $parts['path'];
1527              else {
1528                  $pp = explode( '/', $parts['path'] );
1529                  foreach( $pp as &$p ) {
1530                      $p = strtr( rawurlencode( $p ), array( '%40' => '@' ) );
1531                  }
1532  
1533                  $pp = implode( '/', $pp );
1534                  $out .= $pp;
1535              }
1536          }
1537  
1538          if( $this->addPart( $mask, 'query', $parts ) ) {
1539              $out .= '?' . $parts['query'];
1540          }
1541  
1542          if( $this->addPart( $mask, 'fragment', $parts ) ) {
1543              $out .= '#' . $parts['fragment'];
1544          }
1545  
1546          return $out;
1547      }
1548  
1549  // -------------------------------------------------------------
1550  	function links($text)
1551      {
1552          return preg_replace_callback('/
1553              (^|(?<=[\s>.\(])|[{[]) # $pre
1554              "                      # start
1555              (' . $this->c . ')     # $atts
1556              ([^"]+?)               # $text
1557              (?:\(([^)]+?)\)(?="))? # $title
1558              ":
1559              ('.$this->urlch.'+?)   # $url
1560              (\/)?                  # $slash
1561              ([^'.$this->regex_snippets['wrd'].'\/;]*?)  # $post
1562              ([\]}]|(?=\s|$|\)))       # $tail
1563              /x'.$this->regex_snippets['mod'], array(&$this, "fLink"), $text);
1564      }
1565  
1566  // -------------------------------------------------------------
1567  	function fLink($m)
1568      {
1569          list(, $pre, $atts, $text, $title, $url, $slash, $post, $tail) = $m;
1570  
1571          $uri_parts = array();
1572          $this->parseURI( $url, $uri_parts );
1573  
1574          $scheme         = $uri_parts['scheme'];
1575          $scheme_in_list = in_array( $scheme, $this->url_schemes );
1576          $scheme_ok = '' === $scheme || $scheme_in_list;
1577  
1578          if( !$scheme_ok )
1579              return $m[0];
1580  
1581          if( '$' === $text ) {
1582              if( $scheme_in_list )
1583                  $text = ltrim( $this->rebuildURI( $uri_parts, 'authority,path,query,fragment', false ), '/' );
1584              else
1585                  $text = $url;
1586          }
1587  
1588          $atts = $this->pba($atts);
1589          $atts .= ($title != '') ? ' title="' . $this->encode_html($title) . '"' : '';
1590  
1591          if (!$this->noimage)
1592              $text = $this->image($text);
1593  
1594          $text = $this->span($text);
1595          $text = $this->glyphs($text);
1596          $url  = $this->shelveURL( $this->rebuildURI( $uri_parts ) . $slash );
1597  
1598          $opentag  = '<a href="' . $url . '"' . $atts . $this->rel . '>';
1599          $closetag = '</a>';
1600          $tags     = $this->storeTags($opentag, $closetag);
1601          $out      = $tags['open'].trim($text).$tags['close'];
1602  
1603          if (($pre and !$tail) or ($tail and !$pre))
1604          {
1605              $out = $pre.$out.$post.$tail;
1606              $post = '';
1607          }
1608  
1609          return $this->shelve($out).$post;
1610      }
1611  
1612  // -------------------------------------------------------------
1613  	function getRefs($text)
1614      {
1615          if( $this->restricted )
1616              $pattern = "/^\[(.+)\]((?:http:\/\/|https:\/\/|\/)\S+)(?=\s|$)/Um";
1617          else
1618              $pattern = "/^\[(.+)\]((?:http:\/\/|https:\/\/|tel:|file:|ftp:\/\/|sftp:\/\/|mailto:|callto:|\/)\S+)(?=\s|$)/Um";
1619          return preg_replace_callback( $pattern, array(&$this, "refs"), $text);
1620      }
1621  
1622  // -------------------------------------------------------------
1623  	function refs($m)
1624      {
1625          list(, $flag, $url) = $m;
1626          $uri_parts = array();
1627          $this->parseURI( $url, $uri_parts );
1628          $url = ltrim( $this->rebuildURI( $uri_parts ) ); // encodes URL if needed.
1629          $this->urlrefs[$flag] = $url;
1630          return '';
1631      }
1632  
1633  // -------------------------------------------------------------
1634  	function shelveURL($text)
1635      {
1636          if ('' === $text) return '';
1637          $ref = md5($text);
1638          $this->urlshelf[$ref] = $text;
1639          return 'urlref:'.$ref;
1640      }
1641  
1642  // -------------------------------------------------------------
1643  	function retrieveURLs($text)
1644      {
1645          return preg_replace_callback('/urlref:(\w{32})/',
1646              array(&$this, "retrieveURL"), $text);
1647      }
1648  
1649  // -------------------------------------------------------------
1650  	function retrieveURL($m)
1651      {
1652          $ref = $m[1];
1653          if (!isset($this->urlshelf[$ref]))
1654              return $ref;
1655          $url = $this->urlshelf[$ref];
1656          if (isset($this->urlrefs[$url]))
1657              $url = $this->urlrefs[$url];
1658          return $this->r_encode_html($this->relURL($url));
1659      }
1660  
1661  // -------------------------------------------------------------
1662  	function relURL($url)
1663      {
1664          $parts = @parse_url(urldecode($url));
1665          if ((empty($parts['scheme']) or @$parts['scheme'] == 'http') and
1666               empty($parts['host']) and
1667               preg_match('/^\w/', @$parts['path']))
1668              $url = $this->hu.$url;
1669          if ($this->restricted and !empty($parts['scheme']) and
1670                  !in_array($parts['scheme'], $this->url_schemes))
1671              return '#';
1672          return $url;
1673      }
1674  
1675  // -------------------------------------------------------------
1676  	function isRelURL($url)
1677      {
1678          $parts = @parse_url($url);
1679          return (empty($parts['scheme']) and empty($parts['host']));
1680      }
1681  
1682  // -------------------------------------------------------------
1683  	function image($text)
1684      {
1685          return preg_replace_callback("/
1686              (?:[[{])?           # pre
1687              \!                   # opening !
1688              (\<|\=|\>)?        # optional alignment atts
1689              ($this->c)           # optional style,class atts
1690              (?:\. )?           # optional dot-space
1691              ([^\s(!]+)           # presume this is the src
1692              \s?                # optional space
1693              (?:\(([^\)]+)\))?  # optional title
1694              \!                   # closing
1695              (?::(\S+))?        # optional href
1696              (?:[\]}]|(?=\s|$|\))) # lookahead: space or end of string
1697          /x", array(&$this, "fImage"), $text);
1698      }
1699  
1700  // -------------------------------------------------------------
1701  	function fImage($m)
1702      {
1703          list(, $algn, $atts, $url) = $m;
1704          $url = htmlspecialchars($url);
1705  
1706          $extras = $align = '';
1707          if( '' !== $algn ) {
1708              $vals = array(
1709                  '<' => 'left',
1710                  '=' => 'center',
1711                  '>' => 'right');
1712              if ( isset($vals[$algn]) ) {
1713                  if( 'html5' === $this->doctype )
1714                      $extras = "align-{$vals[$algn]}";
1715                  else
1716                      $align = " align=\"{$vals[$algn]}\"";
1717              }
1718          }
1719          $atts  = $this->pba($atts , '' , 1 , $extras) . $align;
1720  
1721           if(isset($m[4]))
1722           {
1723               $m[4] = htmlspecialchars($m[4]);
1724              $atts .= ' title="' . $m[4] . '" alt="'     . $m[4] . '"';
1725           }
1726           else
1727               $atts .= ' alt=""';
1728  
1729          $size = false;
1730          if ($this->isRelUrl($url))
1731              $size = @getimagesize(realpath($this->doc_root.ltrim($url, $this->ds)));
1732          if ($size) $atts .= " $size[3]";
1733  
1734          $href = (isset($m[5])) ? $this->shelveURL($m[5]) : '';
1735          $url = $this->shelveURL($url);
1736  
1737          $out = array(
1738              ($href) ? '<a href="' . $href . '"' . $this->rel .'>' : '',
1739              '<img src="' . $url . '"' . $atts . ' />',
1740              ($href) ? '</a>' : ''
1741          );
1742  
1743          return $this->shelve(join('',$out));
1744      }
1745  
1746  // -------------------------------------------------------------
1747  	function code($text)
1748      {
1749          $text = $this->doSpecial($text, '<code>', '</code>', 'fCode');
1750          $text = $this->doSpecial($text, '@', '@', 'fCode');
1751          $text = $this->doSpecial($text, '<pre>', '</pre>', 'fPre');
1752          return $text;
1753      }
1754  
1755  // -------------------------------------------------------------
1756  	function fCode($m)
1757      {
1758          @list(, $before, $text, $after) = $m;
1759          return $before.$this->shelve('<code>'.$this->r_encode_html($text).'</code>').$after;
1760      }
1761  
1762  // -------------------------------------------------------------
1763  	function fPre($m)
1764      {
1765          @list(, $before, $text, $after) = $m;
1766          return $before.'<pre>'.$this->shelve($this->r_encode_html($text)).'</pre>'.$after;
1767      }
1768  
1769  // -------------------------------------------------------------
1770  	function shelve($val)
1771      {
1772          $i = uniqid(rand());
1773          $this->shelf[$i] = $val;
1774          return $i;
1775      }
1776  
1777  // -------------------------------------------------------------
1778  	function retrieve($text)
1779      {
1780          if (is_array($this->shelf))
1781              do {
1782                  $old = $text;
1783                  $text = strtr($text, $this->shelf);
1784               } while ($text != $old);
1785  
1786          return $text;
1787      }
1788  
1789  // -------------------------------------------------------------
1790  // NOTE: deprecated
1791  	function incomingEntities($text)
1792      {
1793          return preg_replace("/&(?![#a-z0-9]+;)/i", "x%x%", $text);
1794      }
1795  
1796  // -------------------------------------------------------------
1797  // NOTE: deprecated
1798  	function encodeEntities($text)
1799      {
1800          return (function_exists('mb_encode_numericentity'))
1801          ?     $this->encode_high($text)
1802          :     htmlentities($text, ENT_NOQUOTES, "utf-8");
1803      }
1804  
1805  // -------------------------------------------------------------
1806  // NOTE: deprecated
1807  	function fixEntities($text)
1808      {
1809          /*    de-entify any remaining angle brackets or ampersands */
1810          return str_replace(array("&gt;", "&lt;", "&amp;"),
1811              array(">", "<", "&"), $text);
1812      }
1813  
1814  // -------------------------------------------------------------
1815  	function cleanWhiteSpace($text)
1816      {
1817          $out = preg_replace("/^\xEF\xBB\xBF|\x1A/", '', $text); # Byte order mark (if present)
1818          $out = preg_replace("/\r\n?/", "\n", $out); # DOS and MAC line endings to *NIX style endings
1819          $out = preg_replace("/^[ \t]*\n/m", "\n", $out);    # lines containing only whitespace
1820          $out = preg_replace("/\n{3,}/", "\n\n", $out);    # 3 or more line ends
1821          $out = preg_replace("/^\n*/", "", $out);        # leading blank lines
1822          return $out;
1823      }
1824  
1825  // -------------------------------------------------------------
1826  	function doSpecial($text, $start, $end, $method='fSpecial')
1827      {
1828          return preg_replace_callback('/(^|\s|[[({>])'.preg_quote($start, '/').'(.*?)'.preg_quote($end, '/').'(\s|$|[\])}])?/ms',
1829              array(&$this, $method), $text);
1830      }
1831  
1832  // -------------------------------------------------------------
1833  	function fSpecial($m)
1834      {
1835          // A special block like notextile or code
1836          @list(, $before, $text, $after) = $m;
1837          return $before.$this->shelve($this->encode_html($text)).$after;
1838      }
1839  
1840  // -------------------------------------------------------------
1841  	function noTextile($text)
1842      {
1843           $text = $this->doSpecial($text, '<notextile>', '</notextile>', 'fTextile');
1844           return $this->doSpecial($text, '==', '==', 'fTextile');
1845  
1846      }
1847  
1848  // -------------------------------------------------------------
1849  	function fTextile($m)
1850      {
1851          @list(, $before, $notextile, $after) = $m;
1852          #$notextile = str_replace(array_keys($modifiers), array_values($modifiers), $notextile);
1853          return $before.$this->shelve($notextile).$after;
1854      }
1855  
1856  // -------------------------------------------------------------
1857  	function footnoteRef($text)
1858      {
1859          return preg_replace('/(?<=\S)\[([0-9]+)([\!]?)\](\s)?/Ue',
1860              '$this->footnoteID(\'\1\',\'\2\',\'\3\')', $text);
1861      }
1862  
1863  // -------------------------------------------------------------
1864  	function footnoteID($id, $nolink, $t)
1865      {
1866          $backref = ' ';
1867          if (empty($this->fn[$id])) {
1868              $this->fn[$id] = $a = uniqid(rand());
1869              $backref = ' id="fnrev'.$a.'" ';
1870          }
1871  
1872          $fnid = $this->fn[$id];
1873  
1874          $footref = ( '!' == $nolink ) ? $id : '<a href="#fn'.$fnid.'">'.$id.'</a>';
1875          $backref .= 'class="footnote"';
1876  
1877          $footref = $this->formatFootnote( $footref, $backref, false );
1878  
1879          return $footref;
1880      }
1881  
1882  // -------------------------------------------------------------
1883  	function glyphs($text)
1884      {
1885          // fix: hackish -- adds a space if final char of text is a double quote.
1886          $text = preg_replace('/"\z/', "\" ", $text);
1887  
1888          $text = preg_split("@(<[\w/!?].*>)@Us", $text, -1, PREG_SPLIT_DELIM_CAPTURE);
1889          $i = 0;
1890          foreach($text as $line) {
1891              // text tag text tag text ...
1892              if (++$i % 2) {
1893                  // raw < > & chars are already entity encoded in restricted mode
1894                  if (!$this->restricted) {
1895                      $line = $this->encode_raw_amp($line);
1896                      $line = $this->encode_lt_gt($line);
1897                  }
1898                  $line = preg_replace($this->glyph_search, $this->glyph_replace, $line);
1899              }
1900              $glyph_out[] = $line;
1901          }
1902          return join('', $glyph_out);
1903      }
1904  
1905  // -------------------------------------------------------------
1906  	function replaceGlyphs($text)
1907      {
1908          return preg_replace('/glyph:([^<]+)/','$1',$text);
1909      }
1910  
1911  // -------------------------------------------------------------
1912  	function hAlign($in)
1913      {
1914          $vals = array(
1915              '<'  => 'left',
1916              '='  => 'center',
1917              '>'  => 'right',
1918              '<>' => 'justify');
1919          return (isset($vals[$in])) ? $vals[$in] : '';
1920      }
1921  
1922  // -------------------------------------------------------------
1923  	function vAlign($in)
1924      {
1925          $vals = array(
1926              '^' => 'top',
1927              '-' => 'middle',
1928              '~' => 'bottom');
1929          return (isset($vals[$in])) ? $vals[$in] : '';
1930      }
1931  
1932  // -------------------------------------------------------------
1933  // NOTE: used in notelists
1934  	function encode_high($text, $charset = "UTF-8")
1935      {
1936          return mb_encode_numericentity($text, $this->cmap(), $charset);
1937      }
1938  
1939  // -------------------------------------------------------------
1940  // NOTE: used in notelists
1941  	function decode_high($text, $charset = "UTF-8")
1942      {
1943          return mb_decode_numericentity($text, $this->cmap(), $charset);
1944      }
1945  
1946  // -------------------------------------------------------------
1947  	function cmap()
1948      {
1949          $f = 0xffff;
1950          $cmap = array(
1951              0x0080, 0xffff, 0, $f);
1952          return $cmap;
1953      }
1954  
1955  // -------------------------------------------------------------
1956  	function encode_raw_amp($text)
1957       {
1958          return preg_replace('/&(?!#?[a-z0-9]+;)/i', '&amp;', $text);
1959      }
1960  
1961  // -------------------------------------------------------------
1962  	function encode_lt_gt($text)
1963       {
1964          return strtr($text, array('<' => '&lt;', '>' => '&gt;'));
1965      }
1966  
1967  // -------------------------------------------------------------
1968  	function encode_quot($text)
1969      {
1970          return str_replace('"', '&quot;', $text);
1971      }
1972  
1973  // -------------------------------------------------------------
1974  	function encode_html($str, $quotes=1)
1975      {
1976          $a = array(
1977              '&' => '&amp;',
1978              '<' => '&lt;',
1979              '>' => '&gt;',
1980          );
1981          if ($quotes) $a = $a + array(
1982              "'" => '&#39;', // numeric, as in htmlspecialchars
1983              '"' => '&quot;',
1984          );
1985  
1986          return strtr($str, $a);
1987      }
1988  
1989  // -------------------------------------------------------------
1990  	function r_encode_html($str, $quotes=1)
1991      {
1992          // in restricted mode, all input but quotes has already been escaped
1993          if ($this->restricted)
1994              return $this->encode_quot($str);
1995          return $this->encode_html($str, $quotes);
1996      }
1997  
1998  // -------------------------------------------------------------
1999  	function textile_popup_help($name, $helpvar, $windowW, $windowH)
2000      {
2001          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 />';
2002      }
2003  
2004  // -------------------------------------------------------------
2005  // NOTE: deprecated
2006  	function txtgps($thing)
2007      {
2008          if (isset($_POST[$thing])) {
2009              if (get_magic_quotes_gpc()) {
2010                  return stripslashes($_POST[$thing]);
2011              }
2012              else {
2013                  return $_POST[$thing];
2014              }
2015          }
2016          else {
2017              return '';
2018          }
2019      }
2020  
2021  // -------------------------------------------------------------
2022  // NOTE: deprecated
2023  	function dump()
2024      {
2025          static $bool = array( 0=>'false', 1=>'true' );
2026          foreach (func_get_args() as $a)
2027              echo "\n<pre>",(is_array($a)) ? print_r($a) : ((is_bool($a)) ? $bool[(int)$a] : $a), "</pre>\n";
2028          return $this;
2029      }
2030  
2031  // -------------------------------------------------------------
2032  
2033  	function blockLite($text)
2034      {
2035          $this->btag = array('bq', 'p');
2036          return $this->block($text."\n\n");
2037      }
2038  
2039  
2040  } // end class
2041  

title

Description

title

Description

title

Description

title

title

Body