TextilePHP: Textile.php Source File

Main Page | Class Hierarchy | Class List | File List | Class Members

# Textile.php

Go to the documentation of this file.

    00001 <?php
    00002 // @(#) $Id: Textile_8php-source.html,v 1.4 2005/03/21 14:46:22 jhriggs Exp $
    00003
    00004 /* This program is free software; you can redistribute it and/or modify
    00005  * it under the terms of the GNU General Public License as published by
    00006  * the Free Software Foundation; either version 2 of the License, or
    00007  * (at your option) any later version.
    00008  *
    00009  * This program is distributed in the hope that it will be useful,
    00010  * but WITHOUT ANY WARRANTY; without even the implied warranty of
    00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    00012  * GNU General Public License for more details.
    00013  *
    00014  * You should have received a copy of the GNU General Public License
    00015  * along with this program; if not, write to the Free Software
    00016  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
    00017  */
    00018
    00030 class Textile {
    00037   var $options = array();
    00038
    00046   var $urlre;
    00047
    00055   var $punct;
    00056
    00064   var $valignre;
    00065
    00073   var $tblalignre;
    00074
    00082   var $halignre;
    00083
    00091   var $alignre;
    00092
    00100   var $imgalignre;
    00101
    00109   var $clstypadre;
    00110
    00118   var $clstyre;
    00119
    00127   var $clstyfiltre;
    00128
    00136   var $codere;
    00137
    00145   var $blocktags;
    00146
    00152   var $links = array();
    00153
    00165   var $repl = array();
    00166
    00173   var $tmp = array();
    00174
    00186   function Textile($options = array()) {
    00187     $this->options = $options;
    00188     $this->options['filters'] = ($this->options['filters'] ? $this->options['filters'] : array());
    00189     $this->options['charset'] = ($this->options['charset'] ? $this->options['charset'] : 'iso-8859-1');
    00190     $this->options['char_encoding'] = (isset($this->options['char_encoding']) ? $this->options['char_encoding'] : 1);
    00191     $this->options['do_quotes'] = (isset($this->options['do_quotes']) ? $this->options['do_quotes'] : 1);
    00192     $this->options['trim_spaces'] = (isset($this->options['trim_spaces']) ? $this->options['trim_spaces'] : 0);
    00193     $this->options['smarty_mode'] = (isset($this->options['smarty_mode']) ? $this->options['smarty_mode'] : 1);
    00194     $this->options['preserve_spaces'] = (iset($this->options['preserve_spaces']) ? $this->options['preserve_spaaces'] : 0);
    00195     $this->options['head_offset'] = (isset($this->options['head_offset']) ? $this->options['head_offset'] : 0);
    00196
    00197     if (is_array($this->options['css'])) {
    00198       $this->css($this->options['css']);
    00199     }
    00200     $this->options['macros'] = ($this->options['macros'] ? $this->options['macros'] : $this->default_macros());
    00201     if (isset($this->options['flavor'])) {
    00202       $this->flavor($this->options['flavor']);
    00203     } else {
    00204       $this->flavor('xhtml1/css');
    00205     }
    00206     $this->_create_re();
    00207   } // function Textile
    00208
    00209   // getter/setter methods...
    00210
    00221   function set($opt, $value = NULL) {
    00222     if (is_array($opt)) {
    00223       foreach ($opt as $opt => $value) {
    00224         $this->set($opt, $value);
    00225       }
    00226     } else {
    00227       // the following options have special set methods
    00228       // that activate upon setting:
    00229       if ($opt == 'charset') {
    00230         $this->charset($value);
    00231       } elseif ($opt == 'css') {
    00232         $this->css($value);
    00233       } elseif ($opt == 'flavor') {
    00234         $this->flavor($value);
    00235       } else {
    00236         $this->options[$opt] = $value;
    00237       }
    00238     }
    00239   } // function set
    00240
    00251   function get($opt) {
    00252     return $this->options[$opt];
    00253   } // function get
    00254
    00269   function disable_html($disable_html = NULL) {
    00270     if ($disable_html != NULL) {
    00271       $this->options['disable_html'] = $disable_html;
    00272     }
    00273     return ($this->options['disable_html'] ? $this->options['disable_html'] : 0);
    00274   } // function disable_html
    00275
    00290   function head_offset($head_offset = NULL) {
    00291     if ($head_offset != NULL) {
    00292       $this->options['head_offset'] = $head_offset;
    00293     }
    00294     return ($this->options['head_offset'] ? $this->options['head_offset'] : 0);
    00295   } // function head_offset
    00296
    00313   function flavor($flavor = NULL) {
    00314     if ($flavor != NULL) {
    00315       $this->options['flavor'] = $flavor;
    00316       if (preg_match('/^xhtml(\d)?(\D|$)/', $flavor, $matches)) {
    00317         if ($matches[1] == '2') {
    00318           $this->options['_line_open'] = '<l>';
    00319           $this->options['_line_close'] = '</l>';
    00320           $this->options['_blockcode_open'] = '<blockcode>';
    00321           $this->options['_blockcode_close'] = '</blockcode>';
    00322           $this->options['css_mode'] = 1;
    00323         } else {
    00324           // xhtml 1.x
    00325           $this->options['_line_open'] = '';
    00326           $this->options['_line_close'] = '<br />';
    00327           $this->options['_blockcode_open'] = '<pre><code>';
    00328           $this->options['_blockcode_close'] = '</code></pre>';
    00329           $this->options['css_mode'] = 1;
    00330         }
    00331       } elseif (preg_match('/^html/', $flavor)) {
    00332         $this->options['_line_open'] = '';
    00333         $this->options['_line_close'] = '<br>';
    00334         $this->options['_blockcode_open'] = '<pre><code>';
    00335         $this->options['_blockcode_close'] = '</code></pre>';
    00336         $this->options['css_mode'] = preg_match('/\/css/', $flavor);
    00337       }
    00338       if ($this->options['css_mode'] && !isset($this->options['css'])) { $this->_css_defaults(); }
    00339     }
    00340     return $this->options['flavor'];
    00341   } // function flavor
    00342
    00404   function css($css = NULL) {
    00405     if ($css != NULL) {
    00406       if (is_array($css)) {
    00407         $this->options['css'] = $css;
    00408         $this->options['css_mode'] = 1;
    00409       } else {
    00410         $this->options['css_mode'] = $css;
    00411         if ($this->options['css_mode'] && !isset($this->options['css'])) { $this->_css_defaults(); }
    00412       }
    00413     }
    00414     return ($this->options['css_mode'] ? $this->options['css'] : 0);
    00415   } // function css
    00416
    00433   function charset($charset = NULL) {
    00434     if ($charset != NULL) {
    00435         $this->options['charset'] = $charset;
    00436         if (preg_match('/^utf-?8$/i', $this->options['charset'])) {
    00437           $this->char_encoding(0);
    00438         } else {
    00439           $this->char_encoding(1);
    00440         }
    00441     }
    00442     return $this->options['charset'];
    00443   } // function charset
    00444
    00458   function docroot($docroot = NULL) {
    00459     if ($docroot != NULL) {
    00460       $this->options['docroot'] = $docroot;
    00461     }
    00462     return $this->options['docroot'];
    00463   } // function docroot
    00464
    00478   function trim_spaces($trim_spaces = NULL) {
    00479     if ($trim_spaces != NULL) {
    00480       $this->options['trim_spaces'] = $trim_spaces;
    00481     }
    00482     return $this->options['trim_spaces'];
    00483   } // function trim_spaces
    00484
    00495   function filter_param($filter_param = NULL) {
    00496     if ($filter_param != NULL) {
    00497       $this->options['filter_param'] = $filter_param;
    00498     }
    00499     return $this->options['filter_param'];
    00500   } // function filter_param
    00501
    00518   function preserve_spaces($preserve_spaces = NULL) {
    00519     if ($preserve_spaces != NULL) {
    00520       $this->options['preserve_spaces'] = $preserve_spaces;
    00521     }
    00522     return $this->options['preserve_spaces'];
    00523   } // function preserve_spaces
    00524
    00537   function filters($filters = NULL) {
    00538     if ($filters != NULL) {
    00539       $this->options['filters'] = $filters;
    00540     }
    00541     return $this->options['filters'];
    00542   } // function filters
    00543
    00558   function char_encoding($char_encoding = NULL) {
    00559     if ($char_encoding != NULL) {
    00560       $this->options['char_encoding'] = $char_encoding;
    00561     }
    00562     return $this->options['char_encoding'];
    00563   } // function char_encoding
    00564
    00577   function handle_quotes($do_quotes = NULL) {
    00578     if ($do_quotes != NULL) {
    00579       $this->options['do_quotes'] = $do_quotes;
    00580     }
    00581     return $this->options['do_quotes'];
    00582   } // function handle_quotes
    00583
    00584   // end of getter/setter methods
    00585
    00599   function _create_re() {
    00600     // a URL discovery regex. This is from Mastering Regex from O'Reilly.
    00601     // Some modifications by Brad Choate <brad at bradchoate dot com>
    00602     $this->urlre = '(?:
    00603     # Must start out right...
    00604     (?=[a-zA-Z0-9./#])
    00605     # Match the leading part (proto://hostname, or just hostname)
    00606     (?:
    00607         # ftp://, http://, or https:// leading part
    00608         (?:ftp|https?|telnet|nntp)://(?:\w+(?::\w+)?@)?[-\w]+(?:\.\w[-\w]*)+
    00609         |
    00610         (?:mailto:)?[-\+\w]+@[-\w]+(?:\.\w[-\w]*)+
    00611         |
    00612         # or, try to find a hostname with our more specific sub-expression
    00613         (?i: [a-z0-9] (?:[-a-z0-9]*[a-z0-9])? \. )+ # sub domains
    00614         # Now ending .com, etc. For these, require lowercase
    00615         (?-i: com\b
    00616             | edu\b
    00617             | biz\b
    00618             | gov\b
    00619             | in(?:t|fo)\b # .int or .info
    00620             | mil\b
    00621             | net\b
    00622             | org\b
    00623             | museum\b
    00624             | aero\b
    00625             | coop\b
    00626             | name\b
    00627             | pro\b
    00628             | [a-z][a-z]\b # two-letter country codes
    00629         )
    00630     )?
    00631
    00632     # Allow an optional port number
    00633     (?: : \d+ )?
    00634
    00635     # The rest of the URL is optional, and begins with / . . .
    00636     (?:
    00637      /?
    00638      # The rest are heuristics for what seems to work well
    00639      [^.!,?;:"\'<>()\[\]{}\s\x7F-\xFF]*
    00640      (?:
    00641         [.!,?;:]+  [^.!,?;:"\'<>()\[\]{}\s\x7F-\xFF]+ #\'"
    00642      )*
    00643     )?
    00644 )';
    00645
    00646     $this->punct = '[\!"\#\$%&\'()\*\+,\-\./:;<=>\?@\[\\\\\]\^_`{\|}\~]';
    00647     $this->valignre = '[\-^~]';
    00648     $this->tblalignre = '[<>=]';
    00649     $this->halignre = '(?:<>|[<>=])';
    00650     $this->alignre = '(?:(?:' . $this->valignre . '|<>' . $this->valignre . '?|' . $this->valignre . '?<>|' . $this->valignre . '?' . $this->halignre . '?|' . $this->halignre . '?' . $this->valignre . '?)(?!\w))';
    00651     $this->imgalignre = '(?:(?:[<>]|' . $this->valignre . '){1,2})';
    00652
    00653     $this->clstypadre = '(?:
    00654   (?:\([A-Za-z0-9_\- \#]+\))
    00655   |
    00656   (?:{
    00657       (?: \( [^)]+ \) | [^\}] )+
    00658      })
    00659   |
    00660   (?:\(+? (?![A-Za-z0-9_\-\#]) )
    00661   |
    00662   (?:\)+?)
    00663   |
    00664   (?: \[ [a-zA-Z\-]+? \] )
    00665 )';
    00666
    00667     $this->clstyre = '(?:
    00668   (?:\([A-Za-z0-9_\- \#]+\))
    00669   |
    00670   (?:{
    00671       [A-Za-z0-9_\-](?: \( [^)]+ \) | [^\}] )+
    00672      })
    00673   |
    00674   (?: \[ [a-zA-Z\-]+? \] )
    00675 )';
    00676
    00677     $this->clstyfiltre = '(?:
    00678   (?:\([A-Za-z0-9_\- \#]+\))
    00679   |
    00680   (?:{
    00681       [A-Za-z0-9_\-](?: \( [^)]+ \) | [^\}] )+
    00682      })
    00683   |
    00684   (?:\|[^\|]+\|)
    00685   |
    00686   (?:\(+?(?![A-Za-z0-9_\-\#]))
    00687   |
    00688   (?:\)+)
    00689   |
    00690   (?: \[ [a-zA-Z]+? \] )
    00691 )';
    00692
    00693     $this->codere = '(?:
    00694     (?:
    00695       [\[{]
    00696       @                           # opening
    00697       (?:\[([A-Za-z0-9]+)\])?     # $1: language id
    00698       (.+?)                       # $2: code
    00699       @                           # closing
    00700       [\]}]
    00701     )
    00702     |
    00703     (?:
    00704       (?:^|(?<=[\s\(]))
    00705       @                           # opening
    00706       (?:\[([A-Za-z0-9]+)\])?     # $3: language id
    00707       ([^\s].+?[^\s])             # $4: code itself
    00708       @                           # closing
    00709       (?:$|(?=' . $this->punct . '{1,2}|\s))
    00710     )
    00711 )';
    00712
    00713     $this->blocktags = '
    00714     <
    00715     (( /? ( h[1-6]
    00716      | p
    00717      | pre
    00718      | div
    00719      | table
    00720      | t[rdh]
    00721      | [ou]l
    00722      | li
    00723      | block(?:quote|code)
    00724      | form
    00725      | input
    00726      | select
    00727      | option
    00728      | textarea
    00729      )
    00730     [ >]
    00731     )
    00732     | !--
    00733     )
    00734 ';
    00735   } // function _create_re
    00736
    00746   function process($str) {
    00747     /*
    00748      * Function names in PHP are case insensitive, so function
    00749      * textile() cannot be redefined.  Thus, this PHP implementation
    00750      * will only use process().
    00751      *
    00752      *   return $this->textile($str);
    00753      * } // function process
    00754      *
    00755      * function textile($str) {
    00756      */
    00757
    00758     // quick translator for abbreviated block names
    00759     // to their tag
    00760     $macros = array('bq' => 'blockquote');
    00761
    00762     // an array to hold any portions of the text to be preserved
    00763     // without further processing by Textile
    00764     array_unshift($this->repl, array());
    00765
    00766     // strip out extra newline characters. we're only matching for \n herein
    00767     //$str = preg_replace('!(?:\r?\n|\r)!', "\n", $str);
    00768     $str = preg_replace('!(?:\015?\012|\015)!', "\n", $str);
    00769
    00770     // optionally remove trailing spaces
    00771     if ($this->options['trim_spaces']) { $str = preg_replace('/ +$/m', '', $str); }
    00772
    00773     // preserve contents of the '==', 'pre', 'blockcode' sections
    00774     $str = preg_replace_callback('{(^|\n\n)==(.+?)==($|\n\n)}s',
    00775                                  $this->_cb('"$m[1]\n\n" . $me->_repl($me->repl[0], $me->format_block(array("text" => $m[2]))) . "\n\n$m[3]"'), $str);
    00776
    00777     if (!$this->disable_html()) {
    00778       // preserve style, script tag contents
    00779       $str = preg_replace_callback('!(<(style|script)(?:>| .+?>).*?</\2>)!s', $this->_cb('$me->_repl($me->repl[0], $m[1])'), $str);
    00780
    00781       // preserve HTML comments
    00782       $str = preg_replace_callback('|(<!--.+?-->)|s', $this->_cb('$me->_repl($me->repl[0], $m[1])'), $str);
    00783
    00784       // preserve pre block contents, encode contents by default
    00785       $pre_start = count($this->repl[0]);
    00786       $str = preg_replace_callback('{(<pre(?: [^>]*)?>)(.+?)(</pre>)}s',
    00787                                    $this->_cb('"\n\n" . $me->_repl($me->repl[0], $m[1] . $me->encode_html($m[2], 1) . $m[3]) . "\n\n"'), $str);
    00788       // fix code tags within pre blocks we just saved.
    00789       for ($i = $pre_start; $i < count($this->repl[0]); $i++) {
    00790         $this->repl[0][$i] = preg_replace('|&lt;(/?)code(.*?)&gt;|s', '<$1code$2>', $this->repl[0][$i]);
    00791       }
    00792
    00793       // preserve code blocks by default, encode contents
    00794       $str = preg_replace_callback('{(<code(?: [^>]+)?>)(.+?)(</code>)}s',
    00795                                    $this->_cb('$me->_repl($me->repl[0], $m[1] . $me->encode_html($m[2], 1) . $m[3])'), $str);
    00796
    00797       // encode blockcode tag (an XHTML 2 tag) and encode it's
    00798       // content by default
    00799       $str = preg_replace_callback('{(<blockcode(?: [^>]+)?>)(.+?)(</blockcode>)}s',
    00800                                    $this->_cb('"\n\n" . $me->_repl($me->repl[0], $m[1] . $me->encode_html($m[2], 1) . $m[3]) . "\n\n"'), $str);
    00801
    00802       // preserve PHPish, ASPish code
    00803       $str = preg_replace_callback('!(<([\?%]).*?(\2)>)!s', $this->_cb('$me->_repl($me->repl[0], $m[1])'), $str);
    00804     }
    00805
    00806     // pass through and remove links that follow this format
    00807     // [id_without_spaces (optional title text)]url
    00808     // lines like this are stripped from the content, and can be
    00809     // referred to using the "link text":id_without_spaces syntax
    00810     //$links = array();
    00811     $str = preg_replace_callback('{(?:\n|^) [ ]* \[ ([^ ]+?) [ ]*? (?:\( (.+?) \) )?  \] ((?:(?:ftp|https?|telnet|nntp)://|/)[^ ]+?) [ ]* (\n|$)}mx',
    00812                                  $this->_cb('($me->links[$m[1]] = array("url" => $m[3], "title" => $m[2])) ? $m[4] : $m[4]'), $str);
    00813     //$this->links = $links;
    00814
    00815     // eliminate starting/ending blank lines
    00816     $str = preg_replace('/^\n+/s', '', $str, 1);
    00817     $str = preg_replace('/\n+$/s', '', $str, 1);
    00818
    00819     // split up text into paragraph blocks, capturing newlines too
    00820     $para = preg_split('/(\n{2,})/', $str, -1, PREG_SPLIT_DELIM_CAPTURE);
    00821     unset($block, $bqlang, $filter, $class, $sticky, $lines,
    00822           $style, $stickybuff, $lang, $clear);
    00823
    00824     $out = '';
    00825
    00826     foreach ($para as $para) {
    00827       if (preg_match('/^\n+$/s', $para)) {
    00828         if ($sticky && $stickybuff) {
    00829           $stickybuff .= $para;
    00830         } else {
    00831           $out .= $para;
    00832         }
    00833         continue;
    00834       }
    00835
    00836       if ($sticky) {
    00837         $sticky++;
    00838       } else {
    00839         unset($block);
    00840         unset($class);
    00841         $style = '';
    00842         unset($lang);
    00843       }
    00844
    00845       unset($id, $cite, $align, $padleft, $padright, $lines, $buffer);
    00846       if (preg_match('{^(h[1-6]|p|bq|bc|fn\d+)
    00847                         ((?:' . $this->clstyfiltre . '*|' . $this->halignre . ')*)
    00848                         (\.\.?)
    00849                         (?::(\d+|' . $this->urlre . '))?\ (.*)$}sx', $para, $matches)) {
    00850         if ($sticky) {
    00851           if ($block == 'bc') {
    00852             // close our blockcode section
    00853             $out = preg_replace('/\n\n$/', '', $out, 1);
    00854             $out .= $this->options['_blockcode_close'] . "\n\n";
    00855           } elseif ($block == 'bq') {
    00856             $out = preg_replace('/\n\n$/', '', $out, 1);
    00857             $out .= '</blockquote>' . "\n\n";
    00858           } elseif ($block == 'table') {
    00859             $table_out = $this->format_table(array('text' => $stickybuff));
    00860             if (!$table_out) { $table_out = ''; }
    00861             $out .= $table_out;
    00862             unset($stickybuff);
    00863           } elseif ($block == 'dl') {
    00864             $dl_out = $this->format_deflist(array('text' => $stickybuff));
    00865             if (!$dl_out) { $dl_out = ''; }
    00866             $out .= $dl_out;
    00867             unset($stickybuff);
    00868           }
    00869           $sticky = 0;
    00870         }
    00871         // block macros: h[1-6](class)., bq(class)., bc(class)., p(class).
    00872         //warn "paragraph: [[$para]]\n\tblock: $1\n\tparams: $2\n\tcite: $4";
    00873         $block = $matches[1];
    00874         $params = $matches[2];
    00875         $cite = $matches[4];
    00876         if ($matches[3] == '..') {
    00877           $sticky = 1;
    00878         } else {
    00879           $sticky = 0;
    00880           unset($class);
    00881           unset($bqlang);
    00882           unset($lang);
    00883           $style = '';
    00884           unset($filter);
    00885         }
    00886         if (preg_match('/^h([1-6])$/', $block, $matches2)) {
    00887           if ($this->options['head_offset']) {
    00888             $block = 'h' . ($matches2[1] + $this->options['head_offset']);
    00889           }
    00890         }
    00891         if (preg_match('{(' . $this->halignre . '+)}', $params, $matches2)) {
    00892           $align = $matches2[1];
    00893           $params = preg_replace('{' . $this->halignre . '+}', '', $params, 1);
    00894         }
    00895         if ($params) {
    00896           if (preg_match('/\|(.+)\|/', $params, $matches2)) {
    00897             $filter = $matches2[1];
    00898             $params = preg_replace('/\|.+?\|/', '', $params, 1);
    00899           }
    00900           if (preg_match('/{([^}]+)}/', $params, $matches2)) {
    00901             $style = $matches2[1];
    00902             $style = preg_replace('/\n/', ' ', $style);
    00903             $params = preg_replace('/{[^}]+}/', '', $params);
    00904           }
    00905           if (preg_match('/\(([A-Za-z0-9_\-\ ]+?)(?:\#(.+?))?\)/', $params, $matches2) ||
    00906               preg_match('/\(([A-Za-z0-9_\-\ ]+?)?(?:\#(.+?))\)/', $params, $matches2)) {
    00907             if ($matches2[1] || $matches2[2]) {
    00908               $class = $matches2[1];
    00909               $id = $matches2[2];
    00910               if ($class) {
    00911                 $params = preg_replace('/\([A-Za-z0-9_\-\ ]+?(#.*?)?\)/', '', $params);
    00912               } elseif ($id) {
    00913                 $params = preg_replace('/\(#.+?\)/', '', $params);
    00914               }
    00915             }
    00916           }
    00917           if (preg_match('/(\(+)/', $params, $matches2)) {
    00918             $padleft = strlen($matches2[1]);
    00919             $params = preg_replace('/\(+/', '', $params, 1);
    00920           }
    00921           if (preg_match('/(\)+)/', $params, $matches2)) {
    00922             $padright = strlen($matches2[1]);
    00923             $params = preg_replace('/\)+/', '', $params, 1);
    00924           }
    00925           if (preg_match('/\[(.+?)\]/', $params, $matches2)) {
    00926             $lang = $matches2[1];
    00927             if ($block == 'bc') {
    00928               $bqlang = $lang;
    00929               unset($lang);
    00930             }
    00931             $params = preg_replace('/\[.+?\]/', '', $params, 1);
    00932           }
    00933         }
    00934         // warn "settings:\n\tblock: $block\n\tpadleft: $padleft\n\tpadright: $padright\n\tclass: $class\n\tstyle: $style\n\tid: $id\n\tfilter: $filter\n\talign: $align\n\tlang: $lang\n\tsticky: $sticky";
    00935         $para = $matches[5];
    00936       } elseif (preg_match('|^<textile#(\d+)>$|', $para, $matches)) {
    00937         $buffer = $this->repl[0][$matches[1] - 1];
    00938       } elseif (preg_match('/^clear([<>]+)?\.$/', $para, $matches)) {
    00939         if ($matches[1] == '<') {
    00940           $clear = 'left';
    00941         } elseif ($matches[1] == '>') {
    00942           $clear = 'right';
    00943         } else {
    00944           $clear = 'both';
    00945         }
    00946         continue;
    00947       } elseif ($sticky && $stickybuff &&
    00948                 ($block == 'table' || $block == 'dl')) {
    00949         $stickybuff .= $para;
    00950         continue;
    00951       } elseif (preg_match('{^(?:' . $this->halignre . '|' . $this->clstypadre . '*)*
    00952                               [\*\#]
    00953                               (?:' . $this->halignre . '|' . $this->clstypadre . '*)*
    00954                               \ }x', $para)) {
    00955         // '*', '#' prefix means a list
    00956         $buffer = $this->format_list(array('text' => $para));
    00957       } elseif (preg_match('{^(?:table(?:' . $this->tblalignre . '|' . $this->clstypadre . '*)*
    00958                               (\.\.?)\s+)?
    00959                               (?:_|' . $this->alignre . '|' . $this->clstypadre . '*)*\|}x', $para, $matches)) {
    00960         // handle wiki-style tables
    00961         if ($matches[1] && ($matches[1] == '..')) {
    00962           $block = 'table';
    00963           $stickybuff = $para;
    00964           $sticky = 1;
    00965           continue;
    00966         } else {
    00967           $buffer = $this->format_table(array('text' => $para));
    00968         }
    00969       } elseif (preg_match('{^(?:dl(?:' . $this->clstyre . ')*(\.\.?)\s+)}x', $para, $matches)) {
    00970         // handle definition lists
    00971         if ($matches[1] && ($matches[1] == '..')) {
    00972           $block = 'dl';
    00973           $stickybuff = $para;
    00974           $sticky = 1;
    00975           continue;
    00976         } else {
    00977           $buffer = $this->format_deflist(array('text' => $para));
    00978         }
    00979       }
    00980       if ($buffer) {
    00981         $out .= $buffer;
    00982         continue;
    00983       }
    00984       $lines = preg_split('/\n/', $para);
    00985       if ((count($lines) == 1) && ($lines[0] == '')) {
    00986         continue;
    00987       }
    00988
    00989       $block = ($block ? $block : 'p');
    00990
    00991       $buffer = '';
    00992       $pre = '';
    00993       $post = '';
    00994
    00995       if ($block == 'bc') {
    00996         if ($sticky <= 1) {
    00997           $pre .= $this->options['_blockcode_open'];
    00998           $pre = preg_replace('/>$/s', '', $pre, 1);
    00999           if ($bqlang) { $pre .= " language=\"$bqlang\""; }
    01000           if ($align) {
    01001             $alignment = $this->_halign($align);
    01002             if ($this->options['css_mode']) {
    01003               if (($padleft || $padright) &&
    01004                   (($alignment == 'left') || ($alignment == 'right'))) {
    01005                 $style .= ';float:' . $alignment;
    01006               } else {
    01007                 $style .= ';text-align:' . $alignment;
    01008               }
    01009               $class .= ' ' . ($this->options['css']["class_align_$alignment"] ? $this->options['css']["class_align_$alignment"] : $alignment);
    01010             } else {
    01011               if ($alignment) { $pre .= " align=\"$alignment\""; }
    01012             }
    01013           }
    01014           if ($padleft) { $style .= ";padding-left:${padleft}em"; }
    01015           if ($padright) { $style .= ";padding-right:${padright}em"; }
    01016           if ($clear) { $style .= ";clear:${clear}"; }
    01017           if ($class) { $class = preg_replace('/^ /', '', $class, 1); }
    01018           if ($class) { $pre .= " class=\"$class\""; }
    01019           if ($id) { $pre .= " id=\"$id\""; }
    01020           if ($style) { $style = preg_replace('/^;/', '', $style, 1); }
    01021           if ($style) { $pre .= " style=\"$style\""; }
    01022           if ($lang) { $pre .= " lang=\"$lang\""; }
    01023           $pre .= '>';
    01024           unset($lang);
    01025           unset($bqlang);
    01026           unset($clear);
    01027         }
    01028         $para = preg_replace_callback('{(?:^|(?<=[\s>])|([{[]))
    01029                                         ==(.+?)==
    01030                                         (?:$|([\]}])|(?=' . $this->punct . '{1,2}|\s))}sx',
    01031                                       $this->_cb('$me->_repl($me->repl[0], $me->format_block(array("text" => $m[2], "inline" => 1, "pre" => $m[1], "post" => $m[3])))'), $para);
    01032         $buffer .= $this->encode_html_basic($para, 1);
    01033         $buffer = preg_replace('/&lt;textile#(\d+)&gt;/', '<textile#$1>', $buffer);
    01034         if ($sticky == 0) {
    01035           $post .= $this->options['_blockcode_close'];
    01036         }
    01037         $out .= $pre . $buffer . $post;
    01038         continue;
    01039       } elseif ($block == 'bq') {
    01040         if ($sticky <= 1) {
    01041           $pre .= '<blockquote';
    01042           if ($align) {
    01043             $alignment = $this->_halign($align);
    01044             if ($this->options['css_mode']) {
    01045               if (($padleft || $padright) &&
    01046                   (($alignment == 'left') || ($alignment == 'right'))) {
    01047                 $style .= ';float:' . $alignment;
    01048               } else {
    01049                 $style .= ';text-align:' . $alignment;
    01050               }
    01051               $class .= ' ' . ($this->options['css']["class_align_$alignment"] ? $this->options['css']["class_align_$alignment"] : $alignment);
    01052             } else {
    01053               if ($alignment) { $pre .= " align=\"$alignment\""; }
    01054             }
    01055           }
    01056           if ($padleft) { $style .= ";padding-left:${padleft}em"; }
    01057           if ($padright) { $style .= ";padding-right:${padright}em"; }
    01058           if ($clear) { $style .= ";clear:${clear}"; }
    01059           if ($class) { $class = preg_replace('/^ /', '', $class, 1); }
    01060           if ($class) { $pre .= " class=\"$class\""; }
    01061           if ($id) { $pre .= " id=\"$id\""; }
    01062           if ($style) { $style = preg_replace('/^;/', '', $style, 1); }
    01063           if ($style) { $pre .= " style=\"$style\""; }
    01064           if ($lang) { $pre .= " lang=\"$lang\""; }
    01065           if ($cite) { $pre .= ' cite="' . $this->format_url(array('url' => $cite)) . '"'; }
    01066           $pre .= '>';
    01067           unset($clear);
    01068         }
    01069         $pre .= '<p>';
    01070       } elseif (preg_match('/fn(\d+)/', $block, $matches)) {
    01071         $fnum = $matches[1];
    01072         $pre .= '<p';
    01073         if ($this->options['css']['class_footnote']) { $class .= ' ' . $this->options['css']['class_footnote']; }
    01074         if ($align) {
    01075           $alignment = $this->_halign($align);
    01076           if ($this->options['css_mode']) {
    01077             if (($padleft || $padright) &&
    01078                 (($alignment == 'left') || ($alignment == 'right'))) {
    01079               $style .= ';float:' . $alignment;
    01080             } else {
    01081               $style .= ';text-align:' . $alignment;
    01082             }
    01083             $class .= ($this->options['css']["class_align_$alignment"] ? $this->options['css']["class_align_$alignment"] : $alignment);
    01084           } else {
    01085             $pre .= " align=\"$alignment\"";
    01086           }
    01087         }
    01088         if ($padleft) { $style .= ";padding-left:${padleft}em"; }
    01089         if ($padright) { $style .= ";padding-right:${padright}em"; }
    01090         if ($clear) { $style .= ";clear:${clear}"; }
    01091         if ($class) { $class = preg_replace('/^ /', '', $class, 1); }
    01092         if ($class) { $pre .= " class=\"$class\""; }
    01093         $pre .= ' id="' . ($this->options['css']['id_footnote_prefix'] ? $this->options['css']['id_footnote_prefix'] : 'fn') . $fnum . '"';
    01094         if ($style) { $style = preg_replace('/^;/', '', $style, 1); }
    01095         if ($style) { $pre .= " style=\"$style\""; }
    01096         if ($lang) { $pre .= " lang=\"$lang\""; }
    01097         $pre .= '>';
    01098         $pre .= '<sup>' . $fnum . '</sup> ';
    01099         // we can close like a regular paragraph tag now
    01100         $block = 'p';
    01101         unset($clear);
    01102       } else {
    01103         $pre .= '<' . ($macros[$block] ? $macros[$block] : $block);
    01104         if ($align) {
    01105           $alignment = $this->_halign($align);
    01106           if ($this->options['css_mode']) {
    01107             if (($padleft || $padright) &&
    01108                 (($alignment == 'left') || ($alignment == 'right'))) {
    01109               $style .= ';float:' . $alignment;
    01110             } else {
    01111               $style .= ';text-align:' . $alignment;
    01112             }
    01113             $class .= ' ' . ($this->options['css']["class_align_$alignment"] ? $this->options['css']["class_align_$alignment"] : $alignment);
    01114           } else {
    01115             $pre .= " align=\"$alignment\"";
    01116           }
    01117         }
    01118         if ($padleft) { $style .= ";padding-left:${padleft}em"; }
    01119         if ($padright) { $style .= ";padding-right:${padright}em"; }
    01120         if ($clear) { $style .= ";clear:${clear}"; }
    01121         if ($class) { $class = preg_replace('/^ /', '', $class, 1); }
    01122         if ($class) { $pre .= " class=\"$class\""; }
    01123         if ($id) { $pre .= " id=\"$id\""; }
    01124         if ($style) { $style = preg_replace('/^;/', '', $style, 1); }
    01125         if ($style) { $pre .= " style=\"$style\""; }
    01126         if ($lang) { $pre .= " lang=\"$lang\""; }
    01127         if ($cite && ($block == 'bq')) { $pre .= ' cite="' . $this->format_url(array('url' => $cite)) . '"'; }
    01128         $pre .= '>';
    01129         unset($clear);
    01130       }
    01131
    01132       $buffer = $this->format_paragraph(array('text' => $para));
    01133
    01134       if ($block == 'bq') {
    01135         if (!preg_match('/<p[ >]/', $buffer)) { $post .= '</p>'; }
    01136         if ($sticky == 0) {
    01137           $post .= '</blockquote>';
    01138         }
    01139       } else {
    01140         $post .= '</' . $block . '>';
    01141       }
    01142
    01143       if (preg_match('{' . $this->blocktags . '}x', $buffer)) {
    01144         $buffer = preg_replace('/^\n\n/s', '', $buffer, 1);
    01145         $out .= $buffer;
    01146       } else {
    01147         if ($filter) { $buffer = $this->format_block(array('text' => "|$filter|" . $buffer, 'inline' => 1)); }
    01148         $out .= $pre . $buffer . $post;
    01149       }
    01150     }
    01151
    01152     if ($sticky) {
    01153       if ($block == 'bc') {
    01154         // close our blockcode section
    01155         $out .= $this->options['_blockcode_close']; // . "\n\n";
    01156       } elseif ($block == 'bq') {
    01157         $out .= '</blockquote>'; // . "\n\n";
    01158       } elseif (($block == 'table') && $stickybuff) {
    01159         $table_out = $this->format_table(array('text' => $stickybuff));
    01160         if ($table_out) { $out .= $table_out; }
    01161       } elseif (($block == 'dl') && $stickybuff) {
    01162         $dl_out = $this->format_deflist(array('text' => $stickybuff));
    01163         if ($dl_out) { $out .= $dl_out; }
    01164       }
    01165     }
    01166
    01167     // cleanup-- restore preserved blocks
    01168     for ($i = count($this->repl[0]); $i > 0; $i--) {
    01169       $out = preg_replace('!(?:<|&lt;)textile#' . $i . '(?:>|&gt;)!', str_replace('$', '\\$', $this->repl[0][$i - 1]), $out, 1);
    01170     }
    01171     array_shift($this->repl);
    01172
    01173     // scan for br, hr tags that are not closed and close them
    01174     // only for xhtml! just the common ones -- don't fret over input
    01175     // and the like.
    01176     if (preg_match('/^xhtml/i', $this->flavor())) {
    01177       $out = preg_replace('/(<(?:img|br|hr)[^>]*?(?<!\/))>/', '$1 />', $out);
    01178     }
    01179
    01180     return $out;
    01181   } // function process
    01182
    01202   function format_paragraph($args) {
    01203     $buffer = (isset($args['text']) ? $args['text'] : '');
    01204
    01205     array_unshift($this->repl, array());
    01206     $buffer = preg_replace_callback('{(?:^|(?<=[\s>])|([{[]))
    01207                                       ==(.+?)==
    01208                                       (?:$|([\]}])|(?=' . $this->punct . '{1,2}|\s))}sx',
    01209                                     $this->_cb('$me->_repl($me->repl[0], $me->format_block(array("text" => $m[2], "inline" => 1, "pre" => $m[1], "post" => $m[3])))'), $buffer);
    01210
    01211     unset($tokens);
    01212     if (preg_match('/</', $buffer) && (!$this->disable_html())) {  // optimization -- no point in tokenizing if we
    01213                                        // have no tags to tokenize
    01214       $tokens = $this->_tokenize($buffer);
    01215     } else {
    01216       $tokens = array(array('text', $buffer));
    01217     }
    01218     $result = '';
    01219     foreach ($tokens as $token) {
    01220       $text = $token[1];
    01221       if ($token[0] == 'tag') {
    01222         $text = preg_replace('/&(?!amp;)/', '&amp;', $text);
    01223         $result .= $text;
    01224       } else {
    01225         $text = $this->format_inline(array('text' => $text));
    01226         $result .= $text;
    01227       }
    01228     }
    01229
    01230     // now, add line breaks for lines that contain plaintext
    01231     $lines = preg_split('/\n/', $result);
    01232     $result = '';
    01233     $needs_closing = 0;
    01234     foreach ($lines as $line) {
    01235       if (!preg_match('{(' . $this->blocktags . ')}x', $line)
    01236           && ((preg_match('/^[^<]/', $line) || preg_match('/>[^<]/', $line))
    01237               || !preg_match('/<img /', $line))) {
    01238         if ($this->options['_line_open']) {
    01239           if ($result != '') { $result .= "\n"; }
    01240           $result .= $this->options['_line_open'] . $line . $this->options['_line_close'];
    01241         } else {
    01242           if ($needs_closing) {
    01243             $result .= $this->options['_line_close'] . "\n";
    01244           } else {
    01245             $needs_closing = 1;
    01246             if ($result != '') { $result .= "\n"; }
    01247           }
    01248           $result .= $line;
    01249         }
    01250       } else {
    01251         if ($needs_closing) {
    01252           $result .= $this->options['_line_close'] . "\n";
    01253         } else {
    01254           if ($result != '') { $result .= "\n"; }
    01255         }
    01256         $result .= $line;
    01257         $needs_closing = 0;
    01258       }
    01259     }
    01260
    01261     // at this point, we will restore the \001's to \n's (reversing
    01262     // the step taken in _tokenize).
    01263     //$result = preg_replace('/\r/', "\n", $result);
    01264     $result = preg_replace('/\001/', "\n", $result);
    01265
    01266     for ($i = count($this->repl[0]); $i > 0; $i--) {
    01267       $result = preg_replace("|<textile#$i>|", str_replace('$', '\\$', $this->repl[0][$i - 1]), $result, 1);
    01268     }
    01269     array_shift($this->repl);
    01270
    01271     // quotalize
    01272     if ($this->options['do_quotes']) {
    01273       $result = $this->process_quotes($result);
    01274     }
    01275
    01276     return $result;
    01277   } // function format_paragraph
    01278
    01298   function format_inline($args) {
    01299     $qtags = array(array('**', 'b',      '(?<!\*)\*\*(?!\*)', '\*'),
    01300                    array('__', 'i',      '(?<!_)__(?!_)', '_'),
    01301                    array('??', 'cite',   '\?\?(?!\?)', '\?'),
    01302                    array('*',  'strong', '(?<!\*)\*(?!\*)', '\*'),
    01303                    array('_',  'em',     '(?<!_)_(?!_)', '_'),
    01304                    array('-',  'del',    '(?<!\-)\-(?!\-)', '-'),
    01305                    array('+',  'ins',    '(?<!\+)\+(?!\+)', '\+'),
    01306                    array('++', 'big',    '(?<!\+)\+\+(?!\+)', '\+\+'),
    01307                    array('--', 'small',  '(?<!\-)\-\-(?!\-)', '\-\-'),
    01308                    array('~',  'sub',    '(?<!\~)\~(?![\\\\/~])', '\~'));
    01309     $text = (isset($args['text']) ? $args['text'] : '');
    01310
    01311     array_unshift($this->repl, array());
    01312
    01313     $text = preg_replace_callback('{' . $this->codere . '}mx', $this->_cb('$me->_repl($me->repl[0], $me->format_code(array("text" => $m[2] . $m[4], "lang" => $m[1] . $m[3])))'), $text);
    01314
    01315     // images must be processed before encoding the text since they might
    01316     // have the <, > alignment specifiers...
    01317
    01318     // !blah (alt)! -> image
    01319     $text = preg_replace_callback('{(?:^|(?<=[\s>])|([{[]))      # $1: open brace/bracket
    01320                                     !                            # opening
    01321                                     (' . $this->imgalignre . '?) # $2: optional alignment
    01322                                     (' . $this->clstypadre . '*) # $3: optional CSS class/id
    01323                                     (' . $this->imgalignre . '?) # $4: optional alignment
    01324                                     (?:\s*)                      # space between alignment/css stuff
    01325                                     ([^\s\(!]+)                  # $5: filename
    01326                                     (\s*[^\(!]*(?:\([^\)]+\))?[^!]*) # $6: extras (alt text)
    01327                                     !                            # closing
    01328                                     (?::(\d+|' . $this->urlre . '))? # $7: optional URL
    01329                                     (?:$|([\]}])|(?=' . $this->punct . '{1,2}|\s)) # $8: closing brace/bracket
    01330                                    }mx', $this->_cb('$me->_repl($me->repl[0], $me->format_image(array("pre" => $m[1], "src" => $m[5], "align" => ($m[2] ? $m[2] : $m[4]), "extra" => $m[6], "url" => $m[7], "clsty" => $m[3], "post" => $m[8])))'), $text);
    01331
    01332     $text = preg_replace_callback('{(?:^|(?<=[\s>])|([{[]))     # $1: open brace/bracket
    01333                                     %                           # opening
    01334                                     (' . $this->halignre . '?)  # $2: optional alignment
    01335                                     (' . $this->clstyre . '*)   # $3: optional CSS class/id
    01336                                     (' . $this->halignre . '?)  # $4: optional alignment
    01337                                     (?:\s*)                     # spacing
    01338                                     ([^%]+?)                    # $5: text
    01339                                     %                           # closing
    01340                                     (?::(\d+|' . $this->urlre . '))? # $6: optional URL
    01341                                     (?:$|([]}])|(?=' . $this->punct . '{1,2}|\s)) # $7: closing brace/bracket
    01342                                    }mx', $this->_cb('$me->_repl($me->repl[0], $me->format_span(array("pre" => $m[1], "text" => $m[5], "align" => ($m[2] ? $m[2] : $m[4]), "cite" => $m[6], "clsty" => $m[3], "post" => $m[7])))'), $text);
    01343
    01344     $text = $this->encode_html($text);
    01345     $text = preg_replace('!&lt;textile#(\d+)&gt;!', '<textile#$1>', $text);
    01346     $text = preg_replace('!&amp;quot;!', '&#34;', $text);
    01347     $text = preg_replace('!&amp;(([a-z]+|#\d+);)!', '&$1', $text);
    01348     $text = preg_replace('!&quot;!', '"', $text);
    01349
    01350     // These create markup with entities. Do first and 'save' result for later:
    01351     // "text":url -> hyperlink
    01352     // links with brackets surrounding
    01353     $parenre = '\( (?: [^()] )* \)';
    01354     $text = preg_replace_callback('{(
    01355                                     [{[]
    01356                                     (?:
    01357                                         (?:"                                         # quote character
    01358                                            (' . $this->clstyre . '*)?                # $2: optional CSS class/id
    01359                                            ([^"]+?)                                  # $3: link text
    01360                                            (?:\( ( (?:[^()]|' . $parenre . ')*) \))? # $4: optional link title
    01361                                            "                                         # closing quote
    01362                                         )
    01363                                         |
    01364                                         (?:\'                                        # open single quote
    01365                                            (' . $this->clstyre . '*)?                # $5: optional CSS class/id
    01366                                            ([^\']+?)                                 # $6: link text
    01367                                            (?:\( ( (?:[^()]|' . $parenre . ')*) \))? # $7: optional link title
    01368                                            \'                                        # closing quote
    01369                                         )
    01370                                     )
    01371                                     :(.+?)                                           # $8: URL suffix
    01372                                     [\]}]
    01373                                    )
    01374                                    }mx', $this->_cb('$me->_repl($me->repl[0], $me->format_link(array("text" => $m[1], "linktext" => $m[3] . $m[6], "title" => $me->encode_html_basic($m[4] . $m[7]), "url" => $m[8], "clsty" => $m[2] . $m[5])))'), $text);
    01375
    01376     $text = preg_replace_callback('{((?:^|(?<=[\s>\(]))                              # $1: open brace/bracket
    01377                                     (?: (?:"                                         # quote character "
    01378                                            (' . $this->clstyre . '*)?                # $2: optional CSS class/id
    01379                                            ([^"]+?)                                  # $3: link text "
    01380                                            (?:\( ( (?:[^()]|' . $parenre . ')*) \))? # $4: optional link title
    01381                                            "                                         # closing quote # "
    01382                                         )
    01383                                         |
    01384                                         (?:\'                                        # open single quote \'
    01385                                            (' . $this->clstyre . '*)?                # $5: optional CSS class/id
    01386                                            ([^\']+?)                                 # $6: link text \'
    01387                                            (?:\( ( (?:[^()]|' . $parenre . ')*) \))? # $7: optional link title
    01388                                            \'                                        # closing quote \'
    01389                                         )
    01390                                     )
    01391                                     :(\d+|' . $this->urlre . ')                      # $8: URL suffix
    01392                                     (?:$|(?=' . $this->punct . '{1,2}|\s)))          # $9: closing brace/bracket
    01393                                    }mx', $this->_cb('$me->_repl($me->repl[0], $me->format_link(array("text" => $m[1], "linktext" => $m[3] . $m[6], "title" => $me->encode_html_basic($m[4] . $m[7]), "url" => $m[8], "clsty" => $m[2] . $m[5])))'), $text);
    01394
    01395     if (preg_match('/^xhtml2/', $this->flavor())) {
    01396       // citation with cite link
    01397       $text = preg_replace_callback('{(?:^|(?<=[\s>\'"\(])|([{[]))                   # $1: open brace/bracket \'
    01398                                       \?\?                                           # opening \'??\'
    01399                                       ([^\?]+?)                                      # $2: characters (can\'t contain \'?\')
    01400                                       \?\?                                           # closing \'??\'
    01401                                       :(\d+|' . $this->urlre . ')                    # $3: optional citation URL
    01402                                       (?:$|([\]}])|(?=' . $this->punct . '{1,2}|\s)) # $4: closing brace/bracket
    01403                                      }mx', $this->_cb('$me->_repl($me->repl[0], $me->format_cite(array("pre" => $m[1], "text" => $m[2], "cite" => $m[3], "post" => $m[4])))'), $text);
    01404     }
    01405
    01406     // footnotes
    01407     if (preg_match('/[^ ]\[\d+\]/', $text)) {
    01408       $fntag = '<sup';
    01409       if ($this->options['css']['class_footnote']) { $fntag .= ' class="' . $this->options['css']['class_footnote'] . '"'; }
    01410       $fntag .= '><a href="#' . ($this->options['css']['id_footnote_prefix'] ? $this->options['css']['id_footnote_prefix'] : 'fn');
    01411       $text = preg_replace('|([^ ])\[(\d+)\]|', '$1' . $fntag . '$2">$2</a></sup>', $text);
    01412     }
    01413
    01414     // translate macros:
    01415     $text = preg_replace_callback('{(\{)(.+?)(\})}x',
    01416                                   $this->_cb('$me->format_macro(array("pre" => $m[1], "post" => $m[3], "macro" => $m[2]))'), $text);
    01417
    01418     // these were present with textile 1 and are common enough
    01419     // to not require macro braces...
    01420     // (tm) -> &trade;
    01421     $text = preg_replace('|[\(\[]TM[\)\]]|i', '&#8482;', $text);
    01422     // (c) -> &copy;
    01423     $text = preg_replace('|[\(\[]C[\)\]]|i', '&#169;', $text);
    01424     // (r) -> &reg;
    01425     $text = preg_replace('|[\(\[]R[\)\]]|i', '&#174;', $text);
    01426
    01427     if ($this->preserve_spaces()) {
    01428       // replace two spaces with an em space
    01429       $text = preg_replace('/(?<!\s)\ \ (?!=\s)/', '&#8195;', $text);
    01430     }
    01431
    01432     $redo = preg_match('/[\*_\?\-\+\^\~]/', $text);
    01433     $last = $text;
    01434     while ($redo) {
    01435       // simple replacements...
    01436       $redo = 0;
    01437       foreach ($qtags as $tag) {
    01438         list ($this->tmp['f'][], $this->tmp['r'][], $qf, $cls) = $tag;
    01439         if ($last != ($text = preg_replace_callback('{(?:^|(?<=[\s>\'"])|([{[]))                     # "\' $1 - pre
    01440                                                       ' . $qf . '                                    #
    01441                                                       (?:(' . $this->clstyre . '*))?                 # $2 - attributes
    01442                                                       ([^' . $cls . '\s].*?)                         # $3 - content
    01443                                                       (?<=\S)' . $qf . '                             #
    01444                                                       (?:$|([\]}])|(?=' . $this->punct . '{1,2}|\s)) # $4 - post
    01445                                                      }mx', $this->_cb('$me->format_tag(array("tag" => end($me->tmp["r"]), "marker" => end($me->tmp["f"]), "pre" => $m[1], "text" => $m[3], "clsty" => $m[2], "post" => $m[4]))'), $text))) {
    01446           $redo = ($redo || ($last != $text));
    01447           $last = $text;
    01448         }
    01449         array_pop($this->tmp['f']); array_pop($this->tmp['r']);
    01450       }
    01451     }
    01452
    01453     // superscript is an even simpler replacement...
    01454     $text = preg_replace('/(?<!\^)\^(?!\^)(.+?)(?<!\^)\^(?!\^)/', '<sup>$1</sup>', $text);
    01455
    01456     // ABC(Aye Bee Cee) -> acronym
    01457     $text = preg_replace_callback('{\b([A-Z][A-Za-z0-9]*?[A-Z0-9]+?)\b(?:[(]([^)]*)[)])}',
    01458                                   $this->_cb('$me->_repl($me->repl[0],"<acronym title=\"" . $me->encode_html_basic($m[2]) . "\">$m[1]</acronym>")'), $text);
    01459
    01460     // ABC -> 'capped' span
    01461     if ($this->tmp['caps'][] = $this->options['css']['class_caps']) {
    01462       $text = preg_replace_callback('/(^|[^"][>\s])  # "
    01463                                       ((?:[A-Z](?:[A-Z0-9\.,\']|\&amp;){2,}\ *)+?) # \'
    01464                                       (?=[^A-Z\.0-9]|$)
    01465                                      /mx', $this->_cb('$m[1] . $me->_repl($me->repl[0], "<span class=\"" . end($me->tmp["caps"]) . "\">$m[2]</span>")'), $text);
    01466     }
    01467     array_pop($this->tmp['caps']);
    01468
    01469     // nxn -> n&times;n
    01470     $text = preg_replace('!((?:[0-9\.]0|[1-9]|\d[\'"])\ ?)x(\ ?\d)!', '$1&#215;$2', $text);
    01471
    01472     // translate these entities to the Unicode equivalents:
    01473     $text = preg_replace('/&#133;/', '&#8230;', $text);
    01474     $text = preg_replace('/&#145;/', '&#8216;', $text);
    01475     $text = preg_replace('/&#146;/', '&#8217;', $text);
    01476     $text = preg_replace('/&#147;/', '&#8220;', $text);
    01477     $text = preg_replace('/&#148;/', '&#8221;', $text);
    01478     $text = preg_replace('/&#150;/', '&#8211;', $text);
    01479     $text = preg_replace('/&#151;/', '&#8212;', $text);
    01480
    01481     // Restore replacements done earlier:
    01482     for ($i = count($this->repl[0]); $i > 0; $i--) {
    01483       $text = preg_replace("|<textile#$i>|", str_replace('$', '\\$', $this->repl[0][$i - 1]), $text);
    01484     }
    01485     array_shift($this->repl);
    01486
    01487     // translate entities to characters for highbit stuff since
    01488     // we're using utf8
    01489     // removed for backward compatability with older versions of Perl
    01490     //if (preg_match('/^utf-?8$/i', $this->options['charset'])) {
    01491     //    // translate any unicode entities to native UTF-8
    01492     //    $text = preg_replace('/\&\#(\d+);/e', '($1 > 127) ? pack('U', $1) : chr($1)', $text);
    01493     //}
    01494
    01495     return $text;
    01496   } // function format_inline
    01497
    01530   function format_macro($attrs) {
    01531     $macro = $attrs['macro'];
    01532     if ($this->options['macros'][$macro]) {
    01533       return $this->options['macros'][$macro];
    01534     }
    01535
    01536     return $attrs['pre'] . $macro . $attrs['post'];
    01537   } // function format_macro
    01538
    01570   function format_cite($args) {
    01571     $pre = (isset($args['pre']) ? $args['pre'] : '');
    01572     $text = (isset($args['text']) ? $args['text'] : '');
    01573     $cite = $args['cite'];
    01574     $post = (isset($args['post']) ? $args['post'] : '');
    01575     $this->_strip_borders($pre, $post);
    01576     $tag = $pre . '<cite';
    01577     if (preg_match('/^xhtml2/', $this->flavor()) && $cite) {
    01578       $cite = $this->format_url(array('url' => $cite));
    01579       $tag .= " cite=\"$cite\"";
    01580     } else {
    01581       $post .= ':';
    01582     }
    01583     $tag .= '>';
    01584     return $tag . $this->format_inline(array('text' => $text)) . '</cite>' . $post;
    01585   } // function format_cite
    01586
    01610   function format_code($args) {
    01611     $code = (isset($args['text']) ? $args['text'] : '');
    01612     $lang = $args['lang'];
    01613     $code = $this->encode_html($code, 1);
    01614     $code = preg_replace('/&lt;textile#(\d+)&gt;/', '<textile#$1>', $code);
    01615     $tag = '<code';
    01616     if ($lang) { $tag .= " language=\"$lang\""; }
    01617     return $tag . '>' . $code . '</code>';
    01618   } // function format_code
    01619
    01662   function format_classstyle($clsty = NULL, $class = NULL, $style = NULL) {
    01663     $class = preg_replace('/^ /', '', $class, 1);
    01664
    01665     unset($lang, $padleft, $padright, $id);
    01666     if ($clsty && preg_match('/{([^}]+)}/', $clsty, $matches)) {
    01667       $_style = $matches[1];
    01668       $_style = preg_replace('/\n/', ' ', $_style);
    01669       $style .= ';' . $_style;
    01670       $clsty = preg_replace('/{[^}]+}/', '', $clsty);
    01671     }
    01672     if ($clsty && (preg_match('/\(([A-Za-z0-9_\- ]+?)(?:#(.+?))?\)/', $clsty, $matches) ||
    01673                    preg_match('/\(([A-Za-z0-9_\- ]+?)?(?:#(.+?))\)/', $clsty, $matches))) {
    01674       if ($matches[1] || $matches[2]) {
    01675         if ($class) {
    01676           $class = $matches[1] . ' ' . $class;
    01677         } else {
    01678           $class = $matches[1];
    01679         }
    01680         $id = $matches[2];
    01681         if ($class) {
    01682           $clsty = preg_replace('/\([A-Za-z0-9_\- ]+?(#.*?)?\)/', '', $clsty);
    01683         }
    01684         if ($id) {
    01685           $clsty = preg_replace('/\(#.+?\)/', '', $clsty);
    01686         }
    01687       }
    01688     }
    01689     if ($clsty && preg_match('/(\(+)/', $clsty, $matches)) {
    01690       $padleft = strlen($matches[1]);
    01691       $clsty = preg_replace('/\(+/', '', $clsty, 1);
    01692     }
    01693     if ($clsty && preg_match('/(\)+)/', $clsty, $matches)) {
    01694       $padright = strlen($matches[1]);
    01695       $clsty = preg_replace('/\)+/', '', $clsty, 1);
    01696     }
    01697     if ($clsty && preg_match('/\[(.+?)\]/', $clsty, $matches)) {
    01698       $lang = $matches[1];
    01699       $clsty = preg_replace('/\[.+?\]/', '', $clsty);
    01700     }
    01701     $attrs = '';
    01702     if ($padleft) { $style .= ";padding-left:${padleft}em"; }
    01703     if ($padright) { $style .= ";padding-right:${padright}em"; }
    01704     $style = preg_replace('/^;/', '', $style, 1);
    01705     $class = preg_replace('/^ /', '', $class, 1);
    01706     $class = preg_replace('/ $/', '', $class, 1);
    01707     if ($class) { $attrs .= " class=\"$class\""; }
    01708     if ($id) { $attrs .= " id=\"$id\""; }
    01709     if ($style) { $attrs .= " style=\"$style\""; }
    01710     if ($lang) { $attrs .= " lang=\"$lang\""; }
    01711     $attrs = preg_replace('/^ /', '', $attrs, 1);
    01712     return $attrs;
    01713   } // function format_classstyle
    01714
    01749   function format_tag($args) {
    01750     $tagname = $args['tag'];
    01751     $text = (isset($args['text']) ? $args['text'] : '');
    01752     $pre = (isset($args['pre']) ? $args['pre'] : '');
    01753     $post = (isset($args['post']) ? $args['post'] : '');
    01754     $clsty = (isset($args['clsty']) ? $args['clsty'] : '');
    01755     $this->_strip_borders($pre, $post);
    01756     $tag = "<$tagname";
    01757     $attr = $this->format_classstyle($clsty);
    01758     if ($attr) { $tag .= " $attr"; }
    01759     $tag .= ">$text</$tagname>";
    01760     return $pre . $tag . $post;
    01761   } // function format_tag
    01762
    01782   function format_deflist($args) {
    01783     $str = (isset($args['text']) ? $args['text'] : '');
    01784     unset($clsty);
    01785     $lines = preg_split('/\n/', $str);
    01786     if (preg_match('{^(dl(' . $this->clstyre . '*?)\.\.?(?:\ +|$))}x', $lines[0], $matches)) {
    01787       $clsty = $matches[2];
    01788       $lines[0] = substr($lines[0], strlen($matches[1]));
    01789     }
    01790
    01791     unset($dt, $dd);
    01792     $out = '';
    01793     foreach ($lines as $line) {
    01794       if (preg_match('{^((?:' . $this->clstyre . '*)(?:[^\ ].*?)(?<!["\'\ ])):([^\ \/].*)$}x', $line, $matches)) {
    01795         if ($dt && $dd) { $out .= $this->add_term($dt, $dd); }
    01796         $dt = $matches[1];
    01797         $dd = $matches[2];
    01798       } else {
    01799         $dd .= "\n" . $line;
    01800       }
    01801     }
    01802     if ($dt && $dd) { $out .= $this->add_term($dt, $dd); }
    01803
    01804     $tag = '<dl';
    01805     if ($clsty) { $attr = $this->format_classstyle($clsty); }
    01806     if ($attr) { $tag .= " $attr"; }
    01807     $tag .= '>' . "\n";
    01808
    01809     return $tag . $out . "</dl>\n";
    01810   } // function format_deflist
    01811
    01824   function add_term($dt, $dd) {
    01825     unset($dtattr, $ddattr);
    01826     unset($dtlang);
    01827     if (preg_match('{^(' . $this->clstyre . '*)}x', $dt, $matches)) {
    01828       $param = $matches[1];
    01829       $dtattr = $this->format_classstyle($param);
    01830       if (preg_match('/\[([A-Za-z]+?)\]/', $param, $matches)) {
    01831         $dtlang = $matches[1];
    01832       }
    01833       $dt = substr($dt, strlen($param));
    01834     }
    01835     if (preg_match('{^(' . $this->clstyre . '*)}x', $dd, $matches)) {
    01836       $param = $matches[1];
    01837       // if the language was specified for the term,
    01838       // then apply it to the definition as well (unless
    01839       // already specified of course)
    01840       if ($dtlang && preg_match('/\[([A-Za-z]+?)\]/', $param)) {
    01841         unset($dtlang);
    01842       }
    01843       $ddattr = $this->format_classstyle(($dtlang ? "[$dtlang]" : '') . $param);
    01844       $dd = substr($dd, strlen($param));
    01845     }
    01846     $out = '<dt';
    01847     if ($dtattr) { $out .= " $dtattr"; }
    01848     $out .= '>' . $this->format_inline(array('text' => $dt)) . '</dt>' . "\n";
    01849     if (preg_match('/\n\n/', $dd)) {
    01850       if (preg_match('/\n\n/', $dd)) { $dd = $this->process($dd); }
    01851     } else {
    01852       $dd = $this->format_paragraph(array('text' => $dd));
    01853     }
    01854     $out .= '<dd';
    01855     if ($ddattr) { $out .= " $ddattr"; }
    01856     $out .= '>' . $dd . '</dd>' . "\n";
    01857     return $out;
    01858   } // function add_term
    01859
    01883   function format_list($args) {
    01884     $str = (isset($args['text']) ? $args['text'] : '');
    01885
    01886     $list_tags = array('*' => 'ul', '#' => 'ol');
    01887
    01888     $lines = preg_split('/\n/', $str);
    01889
    01890     unset($stack);
    01891     $last_depth = 0;
    01892     $item = '';
    01893     $out = '';
    01894     foreach ($lines as $line) {
    01895       if (preg_match('{^((?:' . $this->clstypadre . '*|' . $this->halignre . ')*)
    01896                        ([\#\*]+)
    01897                        ((?:' . $this->halignre . '|' . $this->clstypadre . '*)*)
    01898                        \ (.+)$}x', $line, $matches)) {
    01899         if ($item != '') {
    01900           if (preg_match('/\n/', $item)) {
    01901             if ($this->options['_line_open']) {
    01902               $item = preg_replace('/(<li[^>]*>|^)/m', '$1' . $this->options['_line_open'], $item);
    01903               $item = preg_replace('/(\n|$)/s', $this->options['_line_close'] . '$1', $item);
    01904             } else {
    01905               $item = preg_replace('/(\n)/s', $this->options['_line_close'] . '$1', $item);
    01906             }
    01907           }
    01908           $out .= $item;
    01909           $item = '';
    01910         }
    01911         $type = substr($matches[2], 0, 1);
    01912         $depth = strlen($matches[2]);
    01913         $blockparam = $matches[1];
    01914         $itemparam = $matches[3];
    01915         $line = $matches[4];
    01916         unset ($blockclsty, $blockalign, $blockattr, $itemattr, $itemclsty,
    01917                $itemalign);
    01918         if (preg_match('{(' . $this->clstypadre . '+)}x', $blockparam, $matches)) {
    01919           $blockclsty = $matches[1];
    01920         }
    01921         if (preg_match('{(' . $this->halignre . '+)}', $blockparam, $matches)) {
    01922           $blockalign = $matches[1];
    01923         }
    01924         if (preg_match('{(' . $this->clstypadre . '+)}x', $itemparam, $matches)) {
    01925           $itemclsty = $matches[1];
    01926         }
    01927         if (preg_match('{(' . $this->halignre . '+)}', $itemparam, $matches)) {
    01928           $itemalign = $matches[1];
    01929         }
    01930         if ($itemclsty) { $itemattr = $this->format_classstyle($itemclsty); }
    01931         if ($depth > $last_depth) {
    01932           for ($j = $last_depth; $j < $depth; $j++) {
    01933             $out .= "\n<$list_tags[$type]";
    01934             $stack[] = $type;
    01935             if ($blockclsty) {
    01936               $blockattr = $this->format_classstyle($blockclsty);
    01937               if ($blockattr) { $out .= ' ' . $blockattr; }
    01938             }
    01939             $out .= ">\n<li";
    01940             if ($itemattr) { $out .= " $itemattr"; }
    01941             $out .= ">";
    01942           }
    01943         } elseif ($depth < $last_depth) {
    01944           for ($j = $depth; $j < $last_depth; $j++) {
    01945             if ($j == $depth) { $out .= "</li>\n"; }
    01946             $type = array_pop($stack);
    01947             $out .= "</$list_tags[$type]>\n</li>\n";
    01948           }
    01949           if ($depth) {
    01950             $out .= '<li';
    01951             if ($itemattr) { $out .= " $itemattr"; }
    01952             $out .= '>';
    01953           }
    01954         } else {
    01955           $out .= "</li>\n<li";
    01956           if ($itemattr) { $out .= " $itemattr"; }
    01957           $out .= '>';
    01958         }
    01959         $last_depth = $depth;
    01960       }
    01961       if ($item != '') { $item .= "\n"; }
    01962       $item .= $this->format_paragraph(array('text' => $line));
    01963     }
    01964
    01965     if (preg_match('/\n/', $item, $matches)) {
    01966       if ($this->options['_line_open']) {
    01967         $item = preg_replace('/(<li[^>]*>|^)/m', '$1' . $this->options['_line_open'], $item);
    01968         $item = preg_replace('/(\n|$)/s', $this->options['_line_close'] . '$1', $item);
    01969       } else {
    01970         $item = preg_replace('/(\n)/s', $this->options['_line_close'] . '$1', $item);
    01971       }
    01972     }
    01973     $out .= $item;
    01974
    01975     for ($j = 1; $j <= $last_depth; $j++) {
    01976       if ($j == 1) { $out .= '</li>'; }
    01977       $type = array_pop($stack);
    01978       $out .= "\n" . '</' . $list_tags[$type] . '>' . "\n";
    01979       if ($j != $last_depth) { $out .= '</li>'; }
    01980     }
    01981
    01982     return $out . "\n";
    01983   } // function format_list
    01984
    02003   function format_block($args) {
    02004     $str = (isset($args['text']) ? $args['text'] : '');
    02005     $inline = $args['inline'];
    02006     $pre = (isset($args['pre']) ? $args['pre'] : '');
    02007     $post = (isset($args['post']) ? $args['post'] : '');
    02008     $this->_strip_borders($pre, $post);
    02009     $filters = (preg_match('/^(\|(?:(?:[a-z0-9_\-]+)\|)+)/', $str, $matches) ? $matches[1] : '');
    02010     if ($filters) {
    02011       $filtreg = preg_replace('/[^A-Za-z0-9]/', '\\\\$1', $filters);
    02012       $str = preg_replace('/^' . $filtreg . '/', '', $str, 1);
    02013       $filters = preg_replace('/^\|/', '', $filters, 1);
    02014       $filters = preg_replace('/\|$/', '', $filter, 1);
    02015       $filters = preg_split('/\|/', $filters);
    02016       $str = $this->apply_filters(array('text' => $str, 'filters' => $filters));
    02017       $count = count($filters);
    02018       if ($str = preg_replace('!(<p>){' . $count . '}!se', '(++$i ? "$1" : "$1")', $str) && $i) {
    02019         $str = preg_replace('!(</p>){' . $count . '}!s', '$1', $str);
    02020         $str = preg_replace('!(<br( /)?>){' . $count . '}!s', '$1', $str);
    02021       }
    02022     }
    02023     if ($inline) {
    02024       // strip off opening para, closing para, since we're
    02025       // operating within an inline block
    02026       $str = preg_replace('/^\s*<p[^>]*>/', '', $str, 1);
    02027       $str = preg_replace('/<\/p>\s*$/', '', $str, 1);
    02028     }
    02029     return $pre . $str . $post;
    02030   } // function format_block
    02031
    02043   function format_link($args) {
    02044     $text = (isset($args['text']) ? $args['text'] : '');
    02045     $linktext = (isset($args['linktext']) ? $args['linktext'] : '');
    02046     $title = $args['title'];
    02047     $url = $args['url'];
    02048     $clsty = $args['clsty'];
    02049
    02050     if (!$url || ($url == '')) {
    02051       return $text;
    02052     }
    02053     if (isset($this->links) && isset($this->links[$url])) {
    02054       $title = ($title ? $title : $this->links[$url]['title']);
    02055       $url = $this->links[$url]['url'];
    02056     }
    02057     $linktext = preg_replace('/ +$/', '', $linktext, 1);
    02058     $linktext = $this->format_paragraph(array('text' => $linktext));
    02059     $url = $this->format_url(array('linktext' => $linktext, 'url' => $url));
    02060     $tag = "<a href=\"$url\"";
    02061     $attr = $this->format_classstyle($clsty);
    02062     if ($attr) { $tag .= " $attr"; }
    02063     if ($title) {
    02064       $title = preg_replace('/^\s+/', '', $title, 1);
    02065       if (strlen($title)) { $tag .= " title=\"$title\""; }
    02066     }
    02067     $tag .= ">$linktext</a>";
    02068     return $tag;
    02069   } // function format_link
    02070
    02081   function format_url($args) {
    02082     $url = ($args['url'] ? $args['url'] : '');
    02083     if (preg_match('/^(mailto:)?([-\+\w]+@[-\w]+(\.\w[-\w]*)+)$/', $url, $matches)) {
    02084       $url = 'mailto:' . $this->mail_encode($matches[2]);
    02085     }
    02086     if (!preg_match('!^(/|\./|\.\./|#)!', $url)) {
    02087       if (!preg_match('!^(https?|ftp|mailto|nntp|telnet)!', $url)) { $url = "http://$url"; }
    02088     }
    02089     $url = preg_replace('/&(?!amp;)/', '&amp;', $url);
    02090     $url = preg_replace('/\ /', '+', $url);
    02091     $url = preg_replace_callback('/^((?:.+?)\?)(.+)$/', $this->_cb('$m[1] . $me->encode_url($m[2])'), $url);
    02092     return $url;
    02093   } // function format_url
    02094
    02102   function format_span($args) {
    02103     $text = (isset($args['text']) ? $args['text'] : '');
    02104     $pre = (isset($args['pre']) ? $args['pre'] : '');
    02105     $post = (isset($args['post']) ? $args['post'] : '');
    02106     $align = $args['align'];
    02107     $cite = (isset($args['cite']) ? $args['cite'] : '');
    02108     $clsty = $args['clsty'];
    02109     $this->_strip_borders($pre, $post);
    02110     unset($class, $style);
    02111     $tag  = "<span";
    02112     $style = '';
    02113     if ($align) {
    02114       if ($self->options['css_mode']) {
    02115         $alignment = $this->_halign($align);
    02116         if ($alignment) { $style .= ";float:$alignment"; }
    02117         if ($alignment) { $class .= ' ' . $this->options['css']["class_align_$alignment"]; }
    02118       } else {
    02119         $alignment = ($this->_halign($align) ? $this->_halign($align) : $this->_valign($align));
    02120         if ($alignment) { $tag .= " align=\"$alignment\""; }
    02121       }
    02122     }
    02123     $attr = $this->format_classstyle($clsty, $class, $style);
    02124     if ($attr) { $tag .= " $attr"; }
    02125     if ($cite) {
    02126       $cite = preg_replace('/^:/', '', $cite, 1);
    02127       $cite = $this->format_url(array('url' => $cite));
    02128       $tag .= " cite=\"$cite\"";
    02129     }
    02130     return $pre . $tag . '>' . $this->format_paragraph(array('text' => $text)) . '</span>' . $post;
    02131   } // function format_span
    02132
    02186   function format_image($args) {
    02187     $src = (isset($args['src']) ? $args['src'] : '');
    02188     $extra = $args['extra'];
    02189     $align = $args['align'];
    02190     $pre = (isset($args['pre']) ? $args['pre'] : '');
    02191     $post = (isset($args['post']) ? $args['post'] : '');
    02192     $link = $args['url'];
    02193     $clsty = $args['clsty'];
    02194     $this->_strip_borders($pre, $post);
    02195     if (strlen($src) == 0) { return $pre . '!!' . $post; }
    02196     unset($tag);
    02197     if (preg_match('/^xhtml2/', $this->options['flavor'])) {
    02198       unset($type); // poor man's mime typing. need to extend this externally
    02199       if (preg_match('/(?:\.jpeg|\.jpg)$/i', $src)) {
    02200         $type = 'image/jpeg';
    02201       } elseif (preg_match('/\.gif$/i', $src)) {
    02202         $type = 'image/gif';
    02203       } elseif (preg_match('/\.png$/i', $src)) {
    02204         $type = 'image/png';
    02205       } elseif (preg_match('/\.tiff$/i', $src)) {
    02206         $type = 'image/tiff';
    02207       }
    02208       $tag = "<object";
    02209       if ($type) { $tag .= " type=\"$type\""; }
    02210       $tag .= " data=\"$src\"";
    02211     } else {
    02212       $tag = "<img src=\"$src\"";
    02213     }
    02214     unset($class, $style);
    02215     if ($align) {
    02216       if ($this->options['css_mode']) {
    02217         $alignment = $this->_halign($align);
    02218         if ($alignment) { $style .= ";float:$alignment"; }
    02219         if ($alignment) { $class .= ' ' . $alignment; }
    02220         $alignment = $this->_valign($align);
    02221         if ($alignment) {
    02222           $imgvalign = (preg_match('/(top|bottom)/', $alignment) ? 'text-' . $alignment : $alignment);
    02223           if ($imgvalign) { $style .= ";vertical-align:$imgvalign"; }
    02224           if ($alignment) { $class .= ' ' . $this->options['css']["class_align_$alignment"]; }
    02225         }
    02226       } else {
    02227         $alignment = ($this->_halign($align) ? $this->_halign($align) : $this->_valign($align));
    02228         if ($alignment) { $tag .= " align=\"$alignment\""; }
    02229       }
    02230     }
    02231     unset($pctw, $pcth, $w, $h, $alt);
    02232     if ($extra) {
    02233       $alt = (preg_match('/\(([^\)]+)\)/', $extra, $matches) ? $matches[1] : '');
    02234       $extra = preg_replace('/\([^\)]+\)/', '', $extra, 1);
    02235       $pct = (preg_match('/(^|\s)(\d+)%(\s|$)/', $extra, $matches) ? $matches[2] : '');
    02236       if (!$pct) {
    02237         list($pctw, $pcth) = (preg_match('/(^|\s)(\d+)%x(\d+)%(\s|$)/', $extra, $matches) ? array($matches[2], $matches[3]) : NULL);
    02238       } else {
    02239         $pctw = $pcth = $pct;
    02240       }
    02241       if (!$pctw && !$pcth) {
    02242         list($w,$h) = (preg_match('/(^|\s)(\d+|\*)x(\d+|\*)(\s|$)/', $extra, $matches) ? array($matches[2], $matches[3]) : NULL);
    02243         if ($w == '*') { $w = ''; }
    02244         if ($h == '*') { $h = ''; }
    02245         if (!$w) {
    02246           $w = (preg_match('/(^|[,\s])(\d+)w([\s,]|$)/', $extra, $matches) ? $matches[2] : '');
    02247         }
    02248         if (!$h) {
    02249           $h = (preg_match('/(^|[,\s])(\d+)h([\s,]|$)/', $extra, $matches) ? $matches[2] : '');
    02250         }
    02251       }
    02252     }
    02253     $alt = ($alt ? $alt : '');
    02254     if (!preg_match('/^xhtml2/', $this->options['flavor'])) {
    02255       $tag .= ' alt="' . $this->encode_html_basic($alt) . '"';
    02256     }
    02257     if ($w && $h) {
    02258       if (!preg_match('/^xhtml2/', $this->options['flavor'])) {
    02259         $tag .= " height=\"$h\" width=\"$w\"";
    02260       } else {
    02261         $style .= ";height:${h}px;width:${w}px";
    02262       }
    02263     } else {
    02264       list($image_w, $image_h) = $this->image_size($src);
    02265       if (($image_w && $image_h) && ($w || $h)) {
    02266         // image size determined, but only width or height specified
    02267         if ($w && !$h) {
    02268           // width defined, scale down height proportionately
    02269           $h = intval($image_h * ($w / $image_w));
    02270         } elseif ($h && !$w) {
    02271           $w = intval($image_w * ($h / $image_h));
    02272         }
    02273       } else {
    02274         $w = $image_w;
    02275         $h = $image_h;
    02276       }
    02277       if ($w && $h) {
    02278         if ($pctw || $pcth) {
    02279           $w = intval($w * $pctw / 100);
    02280           $h = intval($h * $pcth / 100);
    02281         }
    02282         if (!preg_match('/^xhtml2/', $this->options['flavor'])) {
    02283           $tag .= " height=\"$h\" width=\"$w\"";
    02284         } else {
    02285           $style .= ";height:{$h}px;width:{$w}px";
    02286         }
    02287       }
    02288     }
    02289     $attr = $this->format_classstyle($clsty, $class, $style);
    02290     if ($attr) { $tag .= " $attr"; }
    02291     if (preg_match('/^xhtml2/', $this->options['flavor'])) {
    02292       $tag .= '><p>' . $this->encode_html_basic($alt) . '</p></object>';
    02293     } elseif (preg_match('/^xhtml/', $this->options['flavor'])) {
    02294       $tag .= ' />';
    02295     } else {
    02296       $tag .= '>';
    02297     }
    02298     if ($link) {
    02299       $link = preg_replace('/^:/', '', $link, 1);
    02300       $link = $this->format_url(array('url' => $link));
    02301       $tag = '<a href="' . $link . '">' . $tag . '</a>';
    02302     }
    02303     return $pre . $tag . $post;
    02304   } // function format_image
    02305
    02317   function format_table($args) {
    02318     $str = (isset($args['text']) ? $args['text'] : '');
    02319
    02320     $lines = preg_split('/\n/', $str);
    02321     unset($rows);
    02322     $line_count = count($lines);
    02323     for ($i = 0; $i < $line_count; $i++) {
    02324       if (!preg_match('/\|\s*$/', $lines[$i])) {
    02325         if ($i + 1 < $line_count) {
    02326           if ($i + 1 <= count($lines) - 1) { $lines[$i + 1] = $lines[$i] . "\n" . $lines[$i + 1]; }
    02327         } else {
    02328           $rows[] = $lines[$i];
    02329         }
    02330       } else {
    02331         $rows[] = $lines[$i];
    02332       }
    02333     }
    02334     unset($tid, $tpadl, $tpadr, $tlang);
    02335     $tclass = '';
    02336     $tstyle = '';
    02337     $talign = '';
    02338     if (preg_match('/^table[^\.]/', $rows[0])) {
    02339       $row = $rows[0];
    02340       $row = preg_replace('/^table/', '', $row, 1);
    02341       $params = 1;
    02342       // process row parameters until none are left
    02343       while ($params) {
    02344         if (preg_match('{^(' . $this->tblalignre . ')}', $row, $matches)) {
    02345           // found row alignment
    02346           $talign .= $matches[1];
    02347           if ($matches[1]) { $row = substr($row, strlen($matches[1])); }
    02348           if ($matches[1]) { continue; }
    02349         }
    02350         if (preg_match('{^(' . $this->clstypadre . ')}x', $row, $matches)) {
    02351           // found a class/id/style/padding indicator
    02352           $clsty = $matches[1];
    02353           if ($clsty) { $row = substr($row, strlen($clsty)); }
    02354           if (preg_match('/{([^}]+)}/', $clsty, $matches)) {
    02355             $tstyle = $matches[1];
    02356             $clsty = preg_replace('/{([^}]+)}/', '', $clsty, 1);
    02357             if ($tstyle) { continue; }
    02358           }
    02359           if (preg_match('/\(([A-Za-z0-9_\- ]+?)(?:#(.+?))?\)/', $clsty, $matches) ||
    02360               preg_match('/\(([A-Za-z0-9_\- ]+?)?(?:#(.+?))\)/', $clsty, $matches)) {
    02361             if ($matches[1] || $matches[2]) {
    02362               $tclass = $matches[1];
    02363               $tid = $matches[2];
    02364               continue;
    02365             }
    02366           }
    02367           if (preg_match('/(\(+)/', $clsty, $matches)) { $tpadl = strlen($matches[1]); }
    02368           if (preg_match('/(\)+)/', $clsty, $matches)) { $tpadr = strlen($matches[1]); }
    02369           if (preg_match('/\[(.+?)\]/', $clsty, $matches)) { $tlang = $matches[1]; }
    02370           if ($clsty) { continue; }
    02371         }
    02372         $params = 0;
    02373       }
    02374       $row = preg_replace('/\.\s+/', '', $row, 1);
    02375       $rows[0] = $row;
    02376     }
    02377     $out = '';
    02378     $cols = preg_split('/\|/', $rows[0] . ' ');
    02379     unset($colaligns, $rowspans);
    02380     foreach ($rows as $row) {
    02381       $cols = preg_split('/\|/', $row . ' ');
    02382       $colcount = count($cols) - 1;
    02383       array_pop($cols);
    02384       $colspan = 0;
    02385       $row_out = '';
    02386       unset($rowclass, $rowid, $rowalign, $rowstyle, $rowheader);
    02387       if (!$cols[0]) { $cols[0] = ''; }
    02388       if (preg_match('/_/', $cols[0])) {
    02389         $cols[0] = preg_replace('/_/', '', $cols[0]);
    02390         $rowheader = 1;
    02391       }
    02392       if (preg_match('/{([^}]+)}/', $cols[0], $matches)) {
    02393         $rowstyle = $matches[1];
    02394         $cols[0] = preg_replace('/{[^}]+}/', '', $cols[0]);
    02395       }
    02396       if (preg_match('/\(([^\#]+?)?(#(.+))?\)/', $cols[0], $matches)) {
    02397         $rowclass = $matches[1];
    02398         $rowid = $matches[3];
    02399         $cols[0] = preg_replace('/\([^\)]+\)/', '', $cols[0]);
    02400       }
    02401       if (preg_match('{(' . $this->alignre . ')}', $cols[0], $matches)) { $rowalign = $matches[1]; }
    02402       for ($c = $colcount - 1; $c > 0; $c--) {
    02403         if ($rowspans[$c]) {
    02404           $rowspans[$c]--;
    02405           if ($rowspans[$c] > 1) { continue; }
    02406         }
    02407         unset($colclass, $colid, $header, $colparams, $colpadl, $colpadr, $collang);
    02408         $colstyle = '';
    02409         $colalign = $colaligns[$c];
    02410         $col = array_pop($cols);
    02411         $col = ($col ? $col : '');
    02412         $attrs = '';
    02413         if (preg_match('{^(((_|[/\\\\]\d+|' . $this->alignre . '|' . $this->clstypadre . ')+)\.\ )}x', $col, $matches)) {
    02414           $colparams = $matches[2];
    02415           $col = substr($col, strlen($matches[1]));
    02416           $params = 1;
    02417           // keep processing column parameters until there
    02418           // are none left...
    02419           while ($params) {
    02420             if (preg_match('{^(_|' . $this->alignre . ')(.*)$}', $colparams, $matches)) {
    02421               // found alignment or heading indicator
    02422               $attrs .= $matches[1];
    02423               if ($matches[1]) { $colparams = $matches[2]; }
    02424               if ($matches[1]) { continue; }
    02425             }
    02426             if (preg_match('{^(' . $this->clstypadre . ')(.*)$}x', $colparams, $matches)) {
    02427               // found a class/id/style/padding marker
    02428               $clsty = $matches[1];
    02429               if ($clsty) { $colparams = $matches[2]; }
    02430               if (preg_match('/{([^}]+)}/', $clsty, $matches)) {
    02431                 $colstyle = $matches[1];
    02432                 $clsty = preg_replace('/{([^}]+)}/', '', $clsty, 1);
    02433               }
    02434               if (preg_match('/\(([A-Za-z0-9_\- ]+?)(?:#(.+?))?\)/', $clsty, $matches) ||
    02435                   preg_match('/\(([A-Za-z0-9_\- ]+?)?(?:#(.+?))\)/', $clsty, $matches)) {
    02436                 if ($matches[1] || $matches[2]) {
    02437                   $colclass = $matches[1];
    02438                   $colid = $matches[2];
    02439                   if ($colclass) {
    02440                     $clsty = preg_replace('/\([A-Za-z0-9_\- ]+?(#.*?)?\)/', '', $clsty);
    02441                   } elseif ($colid) {
    02442                     $clsty = preg_replace('/\(#.+?\)/', '', $clsty);
    02443                   }
    02444                 }
    02445               }
    02446               if (preg_match('/(\(+)/', $clsty, $matches)) {
    02447                 $colpadl = strlen($matches[1]);
    02448                 $clsty = preg_replace('/\(+/', '', $clsty, 1);
    02449               }
    02450               if (preg_match('/(\)+)/', $clsty, $matches)) {
    02451                 $colpadr = strlen($matches[1]);
    02452                 $clsty = preg_replace('/\)+/', '', $clsty, 1);
    02453               }
    02454               if (preg_match('/\[(.+?)\]/', $clsty, $matches)) {
    02455                 $collang = $matches[1];
    02456                 $clsty = preg_replace('/\[.+?\]/', '', $clsty, 1);
    02457               }
    02458               if ($clsty) { continue; }
    02459             }
    02460             if (preg_match('/^\\\\(\d+)/', $colparams, $matches)) {
    02461               $colspan = $matches[1];
    02462               $colparams = substr($colparams, strlen($matches[1]) + 1);
    02463               if ($matches[1]) { continue; }
    02464             }
    02465             if (preg_match('/\/(\d+)/', $colparams, $matches)) {
    02466               if ($matches[1]) { $rowspans[$c] = $matches[1]; }
    02467               $colparams = substr($colparams, strlen($matches[1]) + 1);
    02468               if ($matches[1]) { continue; }
    02469             }
    02470             $params = 0;
    02471           }
    02472         }
    02473         if (strlen($attrs)) {
    02474           if (preg_match('/_/', $attrs)) { $header = 1; }
    02475           if (preg_match('{(' . $this->alignre . ')}', $attrs, $matches) && strlen($matches[1])) { $colalign = ''; }
    02476           // determine column alignment
    02477           if (preg_match('/<>/', $attrs)) {
    02478             $colalign .= '<>';
    02479           } elseif (preg_match('/</', $attrs)) {
    02480             $colalign .= '<';
    02481           } elseif (preg_match('/=/', $attrs)) {
    02482             $colalign = '=';
    02483           } elseif (preg_match('/>/', $attrs)) {
    02484             $colalign = '>';
    02485           }
    02486           if (preg_match('/\^/', $attrs)) {
    02487             $colalign .= '^';
    02488           } elseif (preg_match('/~/', $attrs)) {
    02489             $colalign .= '~';
    02490           } elseif (preg_match('/-/', $attrs)) {
    02491             $colalign .= '-';
    02492           }
    02493         }
    02494         if ($rowheader) { $header = 1; }
    02495         if ($header) { $colaligns[$c] = $colalign; }
    02496         $col = preg_replace('/^ +/', '', $col, 1); $col = preg_replace('/ +$/', '', $col, 1);
    02497         if (strlen($col)) {
    02498           // create one cell tag
    02499           $rowspan = ($rowspans[$c] ? $rowspans[$c] : 0);
    02500           $col_out = '<' . ($header ? 'th' : 'td');
    02501           if ($colalign) {
    02502             // horizontal, vertical alignment
    02503             $halign = $this->_halign($colalign);
    02504             if ($halign) { $col_out .= " align=\"$halign\""; }
    02505             $valign = $this->_valign($colalign);
    02506             if ($valign) { $col_out .= " valign=\"$valign\""; }
    02507           }
    02508           // apply css attributes, row, column spans
    02509           if ($colpadl) { $colstyle .= ";padding-left:${colpadl}em"; }
    02510           if ($colpadr) { $colstyle .= ";padding-right:${colpadr}em"; }
    02511           if ($colclass) { $col_out .= " class=\"$colclass\""; }
    02512           if ($colid) { $col_out .= " id=\"$colid\""; }
    02513           if ($colstyle) { $colstyle = preg_replace('/^;/', '', $colstyle, 1); }
    02514           if ($colstyle) { $col_out .= " style=\"$colstyle\""; }
    02515           if ($collang) { $col_out .= " lang=\"$collang\""; }
    02516           if ($colspan > 1) { $col_out .= " colspan=\"$colspan\""; }
    02517           if ($rowspan > 1) { $col_out .= " rowspan=\"$rowspan\""; }
    02518           $col_out .= '>';
    02519           // if the content of this cell has newlines OR matches
    02520           // our paragraph block signature, process it as a full-blown
    02521           // textile document
    02522           if (preg_match('/\n\n/', $col) ||
    02523               preg_match('{^(?:' . $this->halignre . '|' . $this->clstypadre . '*)*
    02524                             [\*\#]
    02525                             (?:' . $this->clstypadre . '*|' . $this->halignre . ')*\ }x', $col)) {
    02526             $col_out .= $this->process($col);
    02527           } else {
    02528             $col_out .= $this->format_paragraph(array('text' => $col));
    02529           }
    02530           $col_out .= '</' . ($header ? 'th' : 'td') . '>';
    02531           $row_out = $col_out . $row_out;
    02532           if ($colspan) { $colspan = 0; }
    02533         } else {
    02534           if ($colspan == 0) { $colspan = 1; }
    02535           $colspan++;
    02536         }
    02537       }
    02538       if ($colspan > 1) {
    02539         // handle the spanned column if we came up short
    02540         $colspan--;
    02541         $row_out = "<td"
    02542                  . ($colspan > 1 ? " colspan=\"$colspan\"" : '')
    02543                  . "></td>$row_out";
    02544       }
    02545
    02546       // build one table row
    02547       $out .= "<tr";
    02548       if ($rowalign) {
    02549         $valign = $this->_valign($rowalign);
    02550         if ($valign) { $out .= " valign=\"$valign\""; }
    02551       }
    02552       if ($rowclass) { $out .= " class=\"$rowclass\""; }
    02553       if ($rowid) { $out .= " id=\"$rowid\""; }
    02554       if ($rowstyle) { $out .= " style=\"$rowstyle\""; }
    02555       $out .= ">$row_out</tr>";
    02556     }
    02557
    02558     // now, form the table tag itself
    02559     $table = '';
    02560     $table .= "<table";
    02561     if ($talign) {
    02562       if ($this->options['css_mode']) {
    02563         // horizontal alignment
    02564         $alignment = $this->_halign($talign);
    02565         if ($talign == '=') {
    02566           $tstyle .= ';margin-left:auto;margin-right:auto';
    02567         } else {
    02568           if ($alignment) { $tstyle .= ';float:' . $alignment; }
    02569         }
    02570         if ($alignment) { $tclass .= ' ' . $alignment; }
    02571       } else {
    02572         $alignment = $this->_halign($talign);
    02573         if ($alignment) { $table .= " align=\"$alignment\""; }
    02574       }
    02575     }
    02576     if ($tpadl) { $tstyle .= ";padding-left:${tpadl}em"; }
    02577     if ($tpadr) { $tstyle .= ";padding-right:${tpadr}em"; }
    02578     if ($tclass) { $tclass = preg_replace('/^ /', '', $tclass, 1); }
    02579     if ($tclass) { $table .= " class=\"$tclass\""; }
    02580     if ($tid) { $table .= " id=\"$tid\""; }
    02581     if ($tstyle) { $tstyle = preg_replace('/^;/', '', $tstyle, 1); }
    02582     if ($tstyle) { $table .= " style=\"$tstyle\""; }
    02583     if ($tlang) { $table .= " lang=\"$tlang\""; }
    02584     if ($tclass || $tid || $tstyle) { $table .= " cellspacing=\"0\""; }
    02585     $table .= ">$out</table>";
    02586
    02587     if (preg_match('|<tr></tr>|', $table)) {
    02588       // exception -- something isn't right so return fail case
    02589       return NULL;
    02590     }
    02591
    02592     return $table;
    02593   } // function format_table
    02594
    02617   function apply_filters($args) {
    02618     $text = $args['text'];
    02619     if (!$text) { return ''; }
    02620     $list = $args['filters'];
    02621     $filters = $this->options['filters'];
    02622     if (!is_array($filters)) { return $text; }
    02623
    02624     $param = $this->filter_param();
    02625     foreach ($list as $filter) {
    02626       if (!isset($filters[$filter])) { continue; }
    02627       if (is_string($filters[$filter])) {
    02628         $text = (($f = create_function('$text, $param', $filters[$filter])) ? $f($text, $param) : $text);
    02629       }
    02630     }
    02631     return $text;
    02632   } // function apply_filters
    02633
    02634   // minor utility / formatting routines
    02635
    02636   var $Have_Entities = 1;
    02637
    02654   function encode_html($html, $can_double_encode = FALSE) {
    02655     if (!$html) { return ''; }
    02656     if ($this->Have_Entities && $this->options['char_encoding']) {
    02657       $html = htmlentities($html);
    02658     } else {
    02659       $html = $this->encode_html_basic($html, $can_double_encode);
    02660     }
    02661     return $html;
    02662   } // function encode_html
    02663
    02674   function decode_html($html) {
    02675     $html = preg_replace('!&quot;!', '"', $html);
    02676     $html = preg_replace('!&amp;!', '&', $html);
    02677     $html = preg_replace('!&lt;!', '<', $html);
    02678     $html = preg_replace('!&gt;!', '>', $html);
    02679     return $html;
    02680   } // function decode_html
    02681
    02698   function encode_html_basic($html, $can_double_encode = FALSE) {
    02699     if (!$html) { return ''; }
    02700     if (!preg_match('/[^\w\s]/', $html)) { return $html; }
    02701     if ($can_double_encode) {
    02702       $html = preg_replace('!&!', '&amp;', $html);
    02703     } else {
    02704       // Encode any & not followed by something that looks like
    02705       // an entity, numeric or otherwise.
    02706       $html = preg_replace('/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w{1,8});)/', '&amp;', $html);
    02707     }
    02708     $html = preg_replace('!"!', '&quot;', $html);
    02709     $html = preg_replace('!<!', '&lt;', $html);
    02710     $html = preg_replace('!>!', '&gt;', $html);
    02711     return $html;
    02712   } // function encode_html_basic
    02713
    02729   function image_size($file) {
    02730     $Have_ImageSize = function_exists('getimagesize');
    02731
    02732     if ($Have_ImageSize) {
    02733       if (file_exists($file)) {
    02734         return @getimagesize($file);
    02735       } else {
    02736         if ($docroot = ($this->docroot() ? $this->docroot() : $_SERVER['DOCUMENT_ROOT'])) {
    02737           $fullpath = $docroot . preg_replace('|^/*(.*)$|', '/$1', $file);
    02738           if (file_exists($fullpath)) {
    02739             return @getimagesize($fullpath);
    02740           }
    02741         }
    02742       }
    02743     }
    02744     return @getimagesize($file);
    02745   } // function image_size
    02746
    02757   function encode_url($str) {
    02758     $str = preg_replace_callback('!([^A-Za-z0-9_\.\-\+\&=%;])!x',
    02759                             $this->_cb('ord($m[1]) > 255 ? \'%u\' . sprintf("%04X", ord($m[1]))
    02760                                                        : \'%\'  . sprintf("%02X", ord($m[1]))'), $str);
    02761     return $str;
    02762   } // function encode_url
    02763
    02773   function mail_encode($addr) {
    02774     // granted, this is simple, but it gives off warm fuzzies
    02775     $addr = preg_replace_callback('!([^\$])!x',
    02776                              $this->_cb('ord($m[1]) > 255 ? \'%u\' . sprintf("%04X", ord($m[1]))
    02777                                                         : \'%\'  . sprintf("%02X", ord($m[1]))'), $addr);
    02778     return $addr;
    02779   } // function mail_encode
    02780
    02790   function process_quotes($str) {
    02791     // stub routine for now. subclass and implement.
    02792     return $str;
    02793   } // function process_quotes
    02794
    02795   // a default set of macros for the {...} macro syntax
    02796   // just a handy way to write a lot of the international characters
    02797   // and some commonly used symbols
    02798
    02807   function default_macros() {
    02808     // <, >, " must be html entities in the macro text since
    02809     // those values are escaped by the time they are processed
    02810     // for macros.
    02811     return array(
    02812       'c|' => '&#162;', // CENT SIGN
    02813       '|c' => '&#162;', // CENT SIGN
    02814       'L-' => '&#163;', // POUND SIGN
    02815       '-L' => '&#163;', // POUND SIGN
    02816       'Y=' => '&#165;', // YEN SIGN
    02817       '=Y' => '&#165;', // YEN SIGN
    02818       '(c)' => '&#169;', // COPYRIGHT SIGN
    02819       '&lt;&lt;' => '&#171;', // LEFT-POINTING DOUBLE ANGLE QUOTATION
    02820       '(r)' => '&#174;', // REGISTERED SIGN
    02821       '+_' => '&#177;', // PLUS-MINUS SIGN
    02822       '_+' => '&#177;', // PLUS-MINUS SIGN
    02823       '&gt;&gt;' => '&#187;', // RIGHT-POINTING DOUBLE ANGLE QUOTATION
    02824       '1/4' => '&#188;', // VULGAR FRACTION ONE QUARTER
    02825       '1/2' => '&#189;', // VULGAR FRACTION ONE HALF
    02826       '3/4' => '&#190;', // VULGAR FRACTION THREE QUARTERS
    02827       'A`' => '&#192;', // LATIN CAPITAL LETTER A WITH GRAVE
    02828       '`A' => '&#192;', // LATIN CAPITAL LETTER A WITH GRAVE
    02829       'A\'' => '&#193;', // LATIN CAPITAL LETTER A WITH ACUTE
    02830       '\'A' => '&#193;', // LATIN CAPITAL LETTER A WITH ACUTE
    02831       'A^' => '&#194;', // LATIN CAPITAL LETTER A WITH CIRCUMFLEX
    02832       '^A' => '&#194;', // LATIN CAPITAL LETTER A WITH CIRCUMFLEX
    02833       'A~' => '&#195;', // LATIN CAPITAL LETTER A WITH TILDE
    02834       '~A' => '&#195;', // LATIN CAPITAL LETTER A WITH TILDE
    02835       'A"' => '&#196;', // LATIN CAPITAL LETTER A WITH DIAERESIS
    02836       '"A' => '&#196;', // LATIN CAPITAL LETTER A WITH DIAERESIS
    02837       'Ao' => '&#197;', // LATIN CAPITAL LETTER A WITH RING ABOVE
    02838       'oA' => '&#197;', // LATIN CAPITAL LETTER A WITH RING ABOVE
    02839       'AE' => '&#198;', // LATIN CAPITAL LETTER AE
    02840       'C,' => '&#199;', // LATIN CAPITAL LETTER C WITH CEDILLA
    02841       ',C' => '&#199;', // LATIN CAPITAL LETTER C WITH CEDILLA
    02842       'E`' => '&#200;', // LATIN CAPITAL LETTER E WITH GRAVE
    02843       '`E' => '&#200;', // LATIN CAPITAL LETTER E WITH GRAVE
    02844       'E\'' => '&#201;', // LATIN CAPITAL LETTER E WITH ACUTE
    02845       '\'E' => '&#201;', // LATIN CAPITAL LETTER E WITH ACUTE
    02846       'E^' => '&#202;', // LATIN CAPITAL LETTER E WITH CIRCUMFLEX
    02847       '^E' => '&#202;', // LATIN CAPITAL LETTER E WITH CIRCUMFLEX
    02848       'E"' => '&#203;', // LATIN CAPITAL LETTER E WITH DIAERESIS
    02849       '"E' => '&#203;', // LATIN CAPITAL LETTER E WITH DIAERESIS
    02850       'I`' => '&#204;', // LATIN CAPITAL LETTER I WITH GRAVE
    02851       '`I' => '&#204;', // LATIN CAPITAL LETTER I WITH GRAVE
    02852       'I\'' => '&#205;', // LATIN CAPITAL LETTER I WITH ACUTE
    02853       '\'I' => '&#205;', // LATIN CAPITAL LETTER I WITH ACUTE
    02854       'I^' => '&#206;', // LATIN CAPITAL LETTER I WITH CIRCUMFLEX
    02855       '^I' => '&#206;', // LATIN CAPITAL LETTER I WITH CIRCUMFLEX
    02856       'I"' => '&#207;', // LATIN CAPITAL LETTER I WITH DIAERESIS
    02857       '"I' => '&#207;', // LATIN CAPITAL LETTER I WITH DIAERESIS
    02858       'D-' => '&#208;', // LATIN CAPITAL LETTER ETH
    02859       '-D' => '&#208;', // LATIN CAPITAL LETTER ETH
    02860       'N~' => '&#209;', // LATIN CAPITAL LETTER N WITH TILDE
    02861       '~N' => '&#209;', // LATIN CAPITAL LETTER N WITH TILDE
    02862       'O`' => '&#210;', // LATIN CAPITAL LETTER O WITH GRAVE
    02863       '`O' => '&#210;', // LATIN CAPITAL LETTER O WITH GRAVE
    02864       'O\'' => '&#211;', // LATIN CAPITAL LETTER O WITH ACUTE
    02865       '\'O' => '&#211;', // LATIN CAPITAL LETTER O WITH ACUTE
    02866       'O^' => '&#212;', // LATIN CAPITAL LETTER O WITH CIRCUMFLEX
    02867       '^O' => '&#212;', // LATIN CAPITAL LETTER O WITH CIRCUMFLEX
    02868       'O~' => '&#213;', // LATIN CAPITAL LETTER O WITH TILDE
    02869       '~O' => '&#213;', // LATIN CAPITAL LETTER O WITH TILDE
    02870       'O"' => '&#214;', // LATIN CAPITAL LETTER O WITH DIAERESIS
    02871       '"O' => '&#214;', // LATIN CAPITAL LETTER O WITH DIAERESIS
    02872       'O/' => '&#216;', // LATIN CAPITAL LETTER O WITH STROKE
    02873       '/O' => '&#216;', // LATIN CAPITAL LETTER O WITH STROKE
    02874       'U`' =>  '&#217;', // LATIN CAPITAL LETTER U WITH GRAVE
    02875       '`U' =>  '&#217;', // LATIN CAPITAL LETTER U WITH GRAVE
    02876       'U\'' => '&#218;', // LATIN CAPITAL LETTER U WITH ACUTE
    02877       '\'U' => '&#218;', // LATIN CAPITAL LETTER U WITH ACUTE
    02878       'U^' => '&#219;', // LATIN CAPITAL LETTER U WITH CIRCUMFLEX
    02879       '^U' => '&#219;', // LATIN CAPITAL LETTER U WITH CIRCUMFLEX
    02880       'U"' => '&#220;', // LATIN CAPITAL LETTER U WITH DIAERESIS
    02881       '"U' => '&#220;', // LATIN CAPITAL LETTER U WITH DIAERESIS
    02882       'Y\'' => '&#221;', // LATIN CAPITAL LETTER Y WITH ACUTE
    02883       '\'Y' => '&#221;', // LATIN CAPITAL LETTER Y WITH ACUTE
    02884       'a`' => '&#224;', // LATIN SMALL LETTER A WITH GRAVE
    02885       '`a' => '&#224;', // LATIN SMALL LETTER A WITH GRAVE
    02886       'a\'' => '&#225;', // LATIN SMALL LETTER A WITH ACUTE
    02887       '\'a' => '&#225;', // LATIN SMALL LETTER A WITH ACUTE
    02888       'a^' => '&#226;', // LATIN SMALL LETTER A WITH CIRCUMFLEX
    02889       '^a' => '&#226;', // LATIN SMALL LETTER A WITH CIRCUMFLEX
    02890       'a~' => '&#227;', // LATIN SMALL LETTER A WITH TILDE
    02891       '~a' => '&#227;', // LATIN SMALL LETTER A WITH TILDE
    02892       'a"' => '&#228;', // LATIN SMALL LETTER A WITH DIAERESIS
    02893       '"a' => '&#228;', // LATIN SMALL LETTER A WITH DIAERESIS
    02894       'ao' => '&#229;', // LATIN SMALL LETTER A WITH RING ABOVE
    02895       'oa' => '&#229;', // LATIN SMALL LETTER A WITH RING ABOVE
    02896       'ae' => '&#230;', // LATIN SMALL LETTER AE
    02897       'c,' => '&#231;', // LATIN SMALL LETTER C WITH CEDILLA
    02898       ',c' => '&#231;', // LATIN SMALL LETTER C WITH CEDILLA
    02899       'e`' => '&#232;', // LATIN SMALL LETTER E WITH GRAVE
    02900       '`e' => '&#232;', // LATIN SMALL LETTER E WITH GRAVE
    02901       'e\'' => '&#233;', // LATIN SMALL LETTER E WITH ACUTE
    02902       '\'e' => '&#233;', // LATIN SMALL LETTER E WITH ACUTE
    02903       'e^' => '&#234;', // LATIN SMALL LETTER E WITH CIRCUMFLEX
    02904       '^e' => '&#234;', // LATIN SMALL LETTER E WITH CIRCUMFLEX
    02905       'e"' => '&#235;', // LATIN SMALL LETTER E WITH DIAERESIS
    02906       '"e' => '&#235;', // LATIN SMALL LETTER E WITH DIAERESIS
    02907       'i`' => '&#236;', // LATIN SMALL LETTER I WITH GRAVE
    02908       '`i' => '&#236;', // LATIN SMALL LETTER I WITH GRAVE
    02909       'i\'' => '&#237;', // LATIN SMALL LETTER I WITH ACUTE
    02910       '\'i' => '&#237;', // LATIN SMALL LETTER I WITH ACUTE
    02911       'i^' => '&#238;', // LATIN SMALL LETTER I WITH CIRCUMFLEX
    02912       '^i' => '&#238;', // LATIN SMALL LETTER I WITH CIRCUMFLEX
    02913       'i"' => '&#239;', // LATIN SMALL LETTER I WITH DIAERESIS
    02914       '"i' => '&#239;', // LATIN SMALL LETTER I WITH DIAERESIS
    02915       'n~' => '&#241;', // LATIN SMALL LETTER N WITH TILDE
    02916       '~n' => '&#241;', // LATIN SMALL LETTER N WITH TILDE
    02917       'o`' => '&#242;', // LATIN SMALL LETTER O WITH GRAVE
    02918       '`o' => '&#242;', // LATIN SMALL LETTER O WITH GRAVE
    02919       'o\'' => '&#243;', // LATIN SMALL LETTER O WITH ACUTE
    02920       '\'o' => '&#243;', // LATIN SMALL LETTER O WITH ACUTE
    02921       'o^' => '&#244;', // LATIN SMALL LETTER O WITH CIRCUMFLEX
    02922       '^o' => '&#244;', // LATIN SMALL LETTER O WITH CIRCUMFLEX
    02923       'o~' => '&#245;', // LATIN SMALL LETTER O WITH TILDE
    02924       '~o' => '&#245;', // LATIN SMALL LETTER O WITH TILDE
    02925       'o"' => '&#246;', // LATIN SMALL LETTER O WITH DIAERESIS
    02926       '"o' => '&#246;', // LATIN SMALL LETTER O WITH DIAERESIS
    02927       ':-' => '&#247;', // DIVISION SIGN
    02928       '-:' => '&#247;', // DIVISION SIGN
    02929       'o/' => '&#248;', // LATIN SMALL LETTER O WITH STROKE
    02930       '/o' => '&#248;', // LATIN SMALL LETTER O WITH STROKE
    02931       'u`' => '&#249;', // LATIN SMALL LETTER U WITH GRAVE
    02932       '`u' => '&#249;', // LATIN SMALL LETTER U WITH GRAVE
    02933       'u\'' => '&#250;', // LATIN SMALL LETTER U WITH ACUTE
    02934       '\'u' => '&#250;', // LATIN SMALL LETTER U WITH ACUTE
    02935       'u^' => '&#251;', // LATIN SMALL LETTER U WITH CIRCUMFLEX
    02936       '^u' => '&#251;', // LATIN SMALL LETTER U WITH CIRCUMFLEX
    02937       'u"' => '&#252;', // LATIN SMALL LETTER U WITH DIAERESIS
    02938       '"u' => '&#252;', // LATIN SMALL LETTER U WITH DIAERESIS
    02939       'y\'' => '&#253;', // LATIN SMALL LETTER Y WITH ACUTE
    02940       '\'y' => '&#253;', // LATIN SMALL LETTER Y WITH ACUTE
    02941       'y"' => '&#255;', // LATIN SMALL LETTER Y WITH DIAERESIS
    02942       '"y' => '&#255;', // LATIN SMALL LETTER Y WITH DIAERESIS
    02943       'OE' => '&#338;', // LATIN CAPITAL LIGATURE OE
    02944       'oe' => '&#339;', // LATIN SMALL LIGATURE OE
    02945       '*' => '&#8226;', // BULLET
    02946       'Fr' => '&#8355;', // FRENCH FRANC SIGN
    02947       'L=' => '&#8356;', // LIRA SIGN
    02948       '=L' => '&#8356;', // LIRA SIGN
    02949       'Rs' => '&#8360;', // RUPEE SIGN
    02950       'C=' => '&#8364;', // EURO SIGN
    02951       '=C' => '&#8364;', // EURO SIGN
    02952       'tm' => '&#8482;', // TRADE MARK SIGN
    02953       '&lt;-' => '&#8592;', // LEFTWARDS ARROW
    02954       '-&gt;' => '&#8594;', // RIGHTWARDS ARROW
    02955       '&lt;=' => '&#8656;', // LEFTWARDS DOUBLE ARROW
    02956       '=&gt;' => '&#8658;', // RIGHTWARDS DOUBLE ARROW
    02957       '=/' => '&#8800;', // NOT EQUAL TO
    02958       '/=' => '&#8800;', // NOT EQUAL TO
    02959       '&lt;_' => '&#8804;', // LESS-THAN OR EQUAL TO
    02960       '_&lt;' => '&#8804;', // LESS-THAN OR EQUAL TO
    02961       '&gt;_' => '&#8805;', // GREATER-THAN OR EQUAL TO
    02962       '_&gt;' => '&#8805;', // GREATER-THAN OR EQUAL TO
    02963       ':(' => '&#9785;', // WHITE FROWNING FACE
    02964       ':)' => '&#9786;', // WHITE SMILING FACE
    02965       'spade' => '&#9824;', // BLACK SPADE SUIT
    02966       'club' => '&#9827;', // BLACK CLUB SUIT
    02967       'heart' => '&#9829;', // BLACK HEART SUIT
    02968       'diamond' => '&#9830;', // BLACK DIAMOND SUIT
    02969     );
    02970   } // function default_macros
    02971
    02972   // "private", internal routines
    02973
    02980   function _css_defaults() {
    02981     $css_defaults = array(
    02982       'class_align_right' => 'right',
    02983       'class_align_left' => 'left',
    02984       'class_align_center' => 'center',
    02985       'class_align_top' => 'top',
    02986       'class_align_bottom' => 'bottom',
    02987       'class_align_middle' => 'middle',
    02988       'class_align_justify' => 'justify',
    02989       'class_caps' => 'caps',
    02990       'class_footnote' => 'footnote',
    02991       'id_footnote_prefix' => 'fn',
    02992     );
    02993     $this->css($css_defaults);
    02994   } // function _css_defaults
    02995
    03025   function _halign($align) {
    03026     if (preg_match('/<>/', $align)) {
    03027       return 'justify';
    03028     } elseif (preg_match('/</', $align)) {
    03029       return 'left';
    03030     } elseif (preg_match('/>/', $align)) {
    03031       return 'right';
    03032     } elseif (preg_match('/=/', $align)) {
    03033       return 'center';
    03034     }
    03035     return '';
    03036   } // function _halign
    03037
    03063   function _valign($align) {
    03064     if (preg_match('/\^/', $align)) {
    03065       return 'top';
    03066     } elseif (preg_match('/~/', $align)) {
    03067       return 'bottom';
    03068     } elseif (preg_match('/-/', $align)) {
    03069       return 'middle';
    03070     }
    03071     return '';
    03072   } // function _valign
    03073
    03109   function _imgalign($align) {
    03110     $align = preg_replace('/(<>|=)/', '', $align);
    03111     return ($this->_valign($align) ? $this->_valign($align) : $this->_halign($align));
    03112   } // function _imgalign
    03113
    03130   function _strip_borders(&$pre, &$post) {
    03131     if ($post && $pre && preg_match('/[{[]/', ($open = substr($pre, 0, 1)))) {
    03132       $close = substr($post, 0, 1);
    03133       if ((($open == '{') && ($close == '}')) ||
    03134           (($open == '[') && ($close == ']'))) {
    03135         $pre = substr($pre, 1);
    03136         $post = substr($post, 1);
    03137       } else {
    03138         if (!preg_match('/[}\]]/', $close)) { $close = substr($post, -1, 1); }
    03139         if ((($open == '{') && ($close == '}')) ||
    03140             (($open == '[') && ($close == ']'))) {
    03141           $pre = substr($pre, 1);
    03142           $post = substr($post, 0, strlen($post) - 1);
    03143         }
    03144       }
    03145     }
    03146   } // function _strip_borders
    03147
    03162   function _repl(&$array, $str) {
    03163     $array[] = $str;
    03164     return '<textile#' . count($array) . '>';
    03165   } // function _repl
    03166
    03177   function _tokenize($str) {
    03178     $pos = 0;
    03179     $len = strlen($str);
    03180     unset($tokens);
    03181
    03182     $depth = 6;
    03183     $nested_tags = substr(str_repeat('(?:</?[A-Za-z0-9:]+ \s? (?:[^<>]|', $depth), 0, -1)
    03184       . str_repeat(')*>)', $depth);
    03185     $match = '(?s: <! ( -- .*? -- \s* )+ > )|  # comment
    03186               (?s: <\? .*? \?> )|              # processing instruction
    03187               (?s: <% .*? %> )|                # ASP-like
    03188               (?:' . $nested_tags . ')|
    03189               (?:' . $this->codere . ')';     // nested tags
    03190
    03191     while (preg_match('{(' . $match . ')}x', substr($str, $pos), $matches, PREG_OFFSET_CAPTURE)) {
    03192       $whole_tag = $matches[1][0];
    03193       $sec_start = $pos + $matches[1][1] + strlen($whole_tag);
    03194       $tag_start = $sec_start - strlen($whole_tag);
    03195       if ($pos < $tag_start) {
    03196         $tokens[] = array('text', substr($str, $pos, $tag_start - $pos));
    03197       }
    03198       if (preg_match('/^[[{]?@/', $whole_tag)) {
    03199         $tokens[] = array('text', $whole_tag);
    03200       } else {
    03201         // this clever hack allows us to preserve \n within tags.
    03202         // this is restored at the end of the format_paragraph method
    03203         //$whole_tag = preg_replace('/\n/', "\r", $whole_tag);
    03204         $whole_tag = preg_replace('/\n/', "\001", $whole_tag);
    03205         $tokens[] = array('tag', $whole_tag);
    03206       }
    03207       $pos = $sec_start;
    03208     }
    03209     if ($pos < $len) { $tokens[] = array('text', substr($str, $pos, $len - $pos)); }
    03210     return $tokens;
    03211   } // function _tokenize
    03212
    03221   function version() {
    03222     /* Why text and an ID?  Well, the text is easier for the user to
    03223      * read and understand while the build ID, being a number (a date
    03224      * with a serial, specifically), is easier for the developer to
    03225      * use to determine newer/older versions for upgrade and
    03226      * installation purposes.
    03227      */
    03228     return array("text" => "2.0.8", "build" => 2005032100);
    03229   } // function version
    03230
    03243   function _cb($function) {
    03244     $current =& Textile::_current_store($this);
    03245     return create_function('$m', '$me =& Textile::_current(); return ' . $function . ';');
    03246   } // function _cb
    03247
    03263  /* static */ function &_current_store(&$new) {
    03264    static $current = array();
    03265
    03266    if ($new != NULL) {
    03267      $current = array(&$new);
    03268    }
    03269
    03270    return $current;
    03271  } // function _current_store
    03272
    03283  /* static */ function &_current() {
    03284    $current =& Textile::_current_store($null = NULL);
    03285    return $current[0];
    03286  } // function _current
    03287 } // class Textile
    03288
    03337 class MTLikeTextile extends Textile {
    03349   function MTLikeTextile($options = array()) {
    03350     parent::Textile($options);
    03351   } // function MTLikeTextile
    03352
    03356   function process_quotes($str) {
    03357     if (!$this->options['do_quotes'] || !function_exists('SmartyPants')) {
    03358       return $str;
    03359     }
    03360
    03361     return SmartyPants($str, $this->options['smarty_mode']);
    03362   } // function process_quotes
    03363
    03367   function format_url($args) {
    03368     $url = ($args['url'] ? $args['url'] : '');
    03369
    03370     if (preg_match('/^(imdb|google|dict|amazon)(:(.+))?$/x', $url, $matches)) {
    03371       $term = $matches[3];
    03372       $term = ($term ? $term : strip_tags($args['linktext']));
    03373
    03374       switch ($matches[1]) {
    03375         case 'imdb':
    03376           $args['url'] = 'http://www.imdb.com/Find?for=' . $term;
    03377           break;
    03378
    03379         case 'google':
    03380           $args['url'] = 'http://www.google.com/search?q=' . $term;
    03381           break;
    03382
    03383         case 'dict':
    03384           $args['url'] = 'http://www.dictionary.com/search?q=' . $term;
    03385           break;
    03386
    03387         case 'amazon':
    03388           $args['url'] = 'http://www.amazon.com/exec/obidos/external-search?index=blended&keyword=' . $term;
    03389           break;
    03390       }
    03391     }
    03392
    03393     return parent::format_url($args);
    03394   } // function format_url
    03395 } // class MTLikeTextile
    03396
    04080 ?>