PHP implementation of Brad Choate's Textile 2. It is feature compatible with the MovableType plugin. Does not play well with the Markdown, Textile, or Textile 2 plugins that ship with WordPress. Packaged by Adam Gessaman. Author: Jim Riggs Author URI: http://jimandlissa.com/project/textilephp */ // @(#) $Id: Textile.php,v 1.5 2004/05/25 15:05:18 jhriggs Exp $ /* This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ function do_textile($text) { // setting $dont_make_text_pretty to 1 will ensure that neither wptexturize() or SmartyPants() functions are run on your text. $dont_make_text_pretty = 0; //$textile = new Textile; $textile = new MTLikeTextile; // head_offset sets the amount to increase header level (i.e. h1. becomes h3. if head_offset == 2) $textile->options['head_offset'] = 0; // $textile->options['char_encoding'] = 'utf-8'; $textile->options['char_encoding'] = false; // do_quotes enables/disables "smart quotes" $textile->options['do_quotes'] = 1; // trim_spaces, if set, will tell textile to clear all lines containing only spaces $textile->options['trim_spaces'] = 0; // enable/disable SmartyPants filtering $textile->options['smarty_mode'] = 1; // if preserve_spaces is 1, whitespace will be encoded to html entities $textile->options['preserve_spaces'] = 0; /** Don't edit below this line **/ if ($dont_make_text_pretty == 1) { $textile->options['smarty_mode'] = 0; return $textile->process($text); } else { return $textile->process($text); } } /** * The Textile class serves as a wrapper for all Textile * functionality. It is not inherently necessary that Textile be a * class; however, this is as close as one can get to a namespace in * PHP. Wrapping the functionality in a class prevents name * collisions and dirtying of the global namespace. The Textile class * uses no global variables and will not have any side-effects on * other code. * * @brief Class wrapper for the Textile functionality. */ class Textile { /** * The @c array containing all of the Textile options for this * object. * * @private */ var $options = array(); /** * The @c string containing the regular expression pattern for a * URL. This variable is initialized by @c _create_re() which is * called in the contructor. * * @private */ var $urlre; /** * The @c string containing the regular expression pattern for * punctuation characters. This variable is initialized by * @c _create_re() which is called in the contructor. * * @private */ var $punct; /** * The @c string containing the regular expression pattern for the * valid vertical alignment codes. This variable is initialized by * @c _create_re() which is called in the contructor. * * @private */ var $valignre; /** * The @c string containing the regular expression pattern for the * valid table alignment codes. This variable is initialized by * @c _create_re() which is called in the contructor. * * @private */ var $tblalignre; /** * The @c string containing the regular expression pattern for the * valid horizontal alignment codes. This variable is initialized by * @c _create_re() which is called in the contructor. * * @private */ var $halignre; /** * The @c string containing the regular expression pattern for the * valid alignment codes. This variable is initialized by * @c _create_re() which is called in the contructor. * * @private */ var $alignre; /** * The @c string containing the regular expression pattern for the * valid image alignment codes. This variable is initialized by * @c _create_re() which is called in the contructor. * * @private */ var $imgalignre; /** * The @c string containing the regular expression pattern for a * class, ID, and/or padding specification. This variable is * initialized by @c _create_re() which is called in the contructor. * * @private */ var $clstypadre; /** * The @c string containing the regular expression pattern for a * class and/or ID specification. This variable is initialized by * @c _create_re() which is called in the contructor. * * @private */ var $clstyre; /** * The @c string containing the regular expression pattern for a * class, ID, and/or filter specification. This variable is * initialized by @c _create_re() which is called in the contructor. * * @private */ var $clstyfiltre; /** * The @c string containing the regular expression pattern for a * code block. This variable is initialized by @c _create_re() which * is called in the contructor. * * @private */ var $codere; /** * The @c string containing the regular expression pattern for all * block tags. This variable is initialized by @c _create_re() which * is called in the contructor. * * @private */ var $blocktags; /** * The @c array containing the list of lookup links. * * @private */ var $links = array(); /** * The @c array containing arrays of replacement blocks * of text that are temporary removed from the input text to avoid * processing. Different functions use this replacement * functionality, and each shifts its own replacement array into * position 0 and removes it when finished. This avoids having * several replacement variables and/or functions clobbering * eachothers' replacement blocks. * * @private */ var $repl = array(); /** * The @c array containing temporary strings used in * replacement callbacks. *JHR* * * @private */ var $tmp = array(); /** * Instantiates a new Textile object. Optional options * can be passed to initialize the object. Attributes for the * options key are the same as the get/set method names * documented here. * * @param $options The @c array specifying the options to use for * this object. * * @public */ function Textile($options = array()) { $this->options = $options; $this->options['filters'] = ($this->options['filters'] ? $this->options['filters'] : array()); $this->options['charset'] = ($this->options['charset'] ? $this->options['charset'] : 'iso-8859-1'); $this->options['char_encoding'] = ($this->options['char_encoding'] ? $this->options['char_encoding'] : 1); $this->options['do_quotes'] = ($this->options['do_quotes'] ? $this->options['do_quotes'] : 1); $this->options['trim_spaces'] = ($this->options['trim_spaces'] ? $this->options['trim_spaces'] : 0); $this->options['smarty_mode'] = ($this->options['smarty_mode'] ? $this->options['smarty_mode'] : 1); $this->options['preserve_spaces'] = ($this->options['preserve_spaces'] ? $this->options['preserve_spaaces'] : 0); $this->options['head_offset'] = ($this->options['head_offset'] ? $this->options['head_offset'] : 0); if (is_array($this->options['css'])) { $this->css($this->options['css']); } $this->options['macros'] = ($this->options['macros'] ? $this->options['macros'] : $this->default_macros()); if ($this->options['flavor']) { $this->flavor($this->options['flavor']); } else { $this->flavor('xhtml1/css'); } $this->_create_re(); } // function Textile // getter/setter methods... /** * Used to set Textile attributes. Attribute names are the same * as the get/set method names documented here. * * @param $opt A @c string specifying the name of the option to * change or an @c array specifying options and values. * @param $value The value for the provided option name. * * @public */ function set($opt, $value = NULL) { if (is_array($opt)) { foreach ($opt as $opt => $value) { $this->set($opt, $value); } } else { // the following options have special set methods // that activate upon setting: if ($opt == 'charset') { $this->charset($value); } elseif ($opt == 'css') { $this->css($value); } elseif ($opt == 'flavor') { $this->flavor($value); } else { $this->options[$opt] = $value; } } } // function set /** * Used to get Textile attributes. Attribute names are the same * as the get/set method names documented here. * * @param $opt A @c string specifying the name of the option to get. * * @return The value for the provided option. * * @public */ function get($opt) { return $this->options[$opt]; } // function get /** * Gets or sets the "disable html" control, which allows you to * prevent HTML tags from being used within the text processed. * Any HTML tags encountered will be removed if disable html is * enabled. Default behavior is to allow HTML. * * @param $disable_html If provided, a @c bool indicating whether or * not this object should disable HTML. * * @return A true value if this object disables HTML; a false value * otherwise. * * @public */ function disable_html($disable_html = NULL) { if ($disable_html != NULL) { $this->options['disable_html'] = $disable_html; } return ($this->options['disable_html'] ? $this->options['disable_html'] : 0); } // function disable_html /** * Gets or sets the relative heading offset, which allows you to * change the heading level used within the text processed. For * example, if the heading offset is '2' and the text contains an * 'h1' block, an \ block will be output. * * @param $head_offset If provided, an @c integer specifying the * heading offset for this object. * * @return An @c integer containing the heading offset for this * object. * * @public */ function head_offset($head_offset = NULL) { if ($head_offset != NULL) { $this->options['head_offset'] = $head_offset; } return ($this->options['head_offset'] ? $this->options['head_offset'] : 0); } // function head_offset /** * Assigns the HTML flavor of output from Textile. Currently * these are the valid choices: html, xhtml (behaves like "xhtml1"), * xhtml1, xhtml2. Default flavor is "xhtml1". * * Note that the xhtml2 flavor support is experimental and incomplete * (and will remain that way until the XHTML 2.0 draft becomes a * proper recommendation). * * @param $flavor If provided, a @c string specifying the flavor to * be used for this object. * * @return A @c string containing the flavor for this object. * * @public */ function flavor($flavor = NULL) { if ($flavor != NULL) { $this->options['flavor'] = $flavor; if (preg_match('/^xhtml(\d)?(\D|$)/', $flavor, $matches)) { if ($matches[1] == '2') { $this->options['_line_open'] = ''; $this->options['_line_close'] = ''; $this->options['_blockcode_open'] = ''; $this->options['_blockcode_close'] = ''; $this->options['css_mode'] = 1; } else { // xhtml 1.x $this->options['_line_open'] = ''; $this->options['_line_close'] = '
'; $this->options['_blockcode_open'] = '
';
          $this->options['_blockcode_close'] = '
'; $this->options['css_mode'] = 1; } } elseif (preg_match('/^html/', $flavor)) { $this->options['_line_open'] = ''; $this->options['_line_close'] = '
'; $this->options['_blockcode_open'] = '
';
        $this->options['_blockcode_close'] = '
'; $this->options['css_mode'] = preg_match('/\/css/', $flavor); } if ($this->options['css_mode'] && !$this->options['css']) { $this->_css_defaults(); } } return $this->options['flavor']; } // function flavor /** * Gets or sets the css support for Textile. If css is enabled, * Textile will emit CSS rules. You may pass a 1 or 0 to enable * or disable CSS behavior altogether. If you pass an associative array, * you may assign the CSS class names that are used by * Textile. The following key names for such an array are * recognized: * * * * @param $css If provided, either a @c bool indicating whether or * not this object should use css or an associative @c array * specifying class names to use. * * @return Either an associative @c array containing class names * used by this object, or a true or false value indicating * whether or not this object uses css. * * @public */ function css($css = NULL) { if ($css != NULL) { if (is_array($css)) { $this->options['css'] = $css; $this->options['css_mode'] = 1; } else { $this->options['css_mode'] = $css; if ($this->options['css_mode'] && !$this->options['css']) { $this->_css_defaults(); } } } return ($this->options['css_mode'] ? $this->options['css'] : 0); } // function css /** * Gets or sets the character set targetted for publication. * At this time, Textile only changes its behavior * if the 'utf-8' character set is assigned. * * Specifically, if utf-8 is requested, any special characters * created by Textile will be output as native utf-8 characters * rather than HTML entities. * * @param $charset If provided, a @c string specifying the * characater set to be used for this object. * * @return A @c string containing the character set for this object. * * @public */ function charset($charset = NULL) { if ($charset != NULL) { $this->options['charset'] = $charset; if (preg_match('/^utf-?8$/i', $this->options['charset'])) { $this->char_encoding(0); } else { $this->char_encoding(1); } } return $this->options['charset']; } // function charset /** * Gets or sets the physical file path to root of document files. * This path is utilized when images are referenced and size * calculations are needed (the getimagesize() function is used to read * the image dimensions). * * @param $docroot If provided, a @c string specifying the document * root to use for this object. * * @return A @c string containing the docroot for this object. * * @public */ function docroot($docroot = NULL) { if ($docroot != NULL) { $this->options['docroot'] = $docroot; } return $this->options['docroot']; } // function docroot /** * Gets or sets the 'trim spaces' control flag. If enabled, this * will clear any lines that have only spaces on them (the newline * itself will remain). * * @param $trim_spaces If provided, a @c bool indicating whether or * not this object should trim spaces. * * @return A true value if this object trims spaces; a false value * otherwise. * * @public */ function trim_spaces($trim_spaces = NULL) { if ($trim_spaces != NULL) { $this->options['trim_spaces'] = $trim_spaces; } return $this->options['trim_spaces']; } // function trim_spaces /** * Gets or sets a parameter that is passed to filters. * * @param $filter_param If provided, a parameter that this object * should pass to filters. * * @return The parameter this object passes to filters. * * @public */ function filter_param($filter_param = NULL) { if ($filter_param != NULL) { $this->options['filter_param'] = $filter_param; } return $this->options['filter_param']; } // function filter_param /** * Gets or sets the 'preserve spaces' control flag. If enabled, this * will replace any double spaces within the paragraph data with the * \&#8195; HTML entity (wide space). The default is 0. Spaces will * pass through to the browser unchanged and render as a single space. * Note that this setting has no effect on spaces within \, * \ blocks or \ sections. * * @param $preserve_spaces If provided, a @c bool indicating whether * or not this object should preserve spaces. * * @return A true value if this object preserves spaces; a false * value otherwise. * * @public */ function preserve_spaces($preserve_spaces = NULL) { if ($preserve_spaces != NULL) { $this->options['preserve_spaces'] = $preserve_spaces; } return $this->options['preserve_spaces']; } // function preserve_spaces /** * Gets or sets a list of filters to make available for * Textile to use. Returns a hash reference of the currently * assigned filters. * * @param $filters If provided, an @c array of filters to be used * for this object. * * @return An @c array containing the filters for this object. * * @public */ function filters($filters = NULL) { if ($filters != NULL) { $this->options['filters'] = $filters; } return $this->options['filters']; } // function filters /** * Gets or sets the character encoding logical flag. If character * encoding is enabled, the htmlentities function is used to * encode special characters. If character encoding is disabled, * only \<, \>, " and & are encoded to HTML entities. * * @param $char_encoding If provided, a @c bool indicating whether * or not this object should encode special characters. * * @return A true value if this object encodes special characters; a * false value otherwise. * * @public */ function char_encoding($char_encoding = NULL) { if ($char_encoding != NULL) { $this->options['char_encoding'] = $char_encoding; } return $this->options['char_encoding']; } // function char_encoding /** * Gets or sets the "smart quoting" control flag. Returns the * current setting. * * @param $do_quotes If provided, a @c bool indicating whether or * not this object should use smart quoting. * * @return A true value if this object uses smart quoting; a false * value otherwise. * * @public */ function handle_quotes($do_quotes = NULL) { if ($do_quotes != NULL) { $this->options['do_quotes'] = $do_quotes; } return $this->options['do_quotes']; } // function handle_quotes // end of getter/setter methods /** * Creates the class variable regular expression patterns used by * Textile. They are not initialized in the declaration, because * some rely on the others, requiring a @c $this reference. * * PHP does not have the Perl qr operator to quote or precompile * patterns, so to avoid escaping and matching problems, all * patterns must use the same delimiter; this implementation uses * {}. Every use of these patterns within this class has been * changed to use these delimiters. *JHR* * * @private */ function _create_re() { // a URL discovery regex. This is from Mastering Regex from O'Reilly. // Some modifications by Brad Choate $this->urlre = '(?: # Must start out right... (?=[a-zA-Z0-9./#]) # Match the leading part (proto://hostname, or just hostname) (?: # ftp://, http://, or https:// leading part (?:ftp|https?|telnet|nntp)://(?:\w+(?::\w+)?@)?[-\w]+(?:\.\w[-\w]*)+ | (?:mailto:)?[-\+\w]+@[-\w]+(?:\.\w[-\w]*)+ | # or, try to find a hostname with our more specific sub-expression (?i: [a-z0-9] (?:[-a-z0-9]*[a-z0-9])? \. )+ # sub domains # Now ending .com, etc. For these, require lowercase (?-i: com\b | edu\b | biz\b | gov\b | in(?:t|fo)\b # .int or .info | mil\b | net\b | org\b | museum\b | aero\b | coop\b | name\b | pro\b | [a-z][a-z]\b # two-letter country codes ) )? # Allow an optional port number (?: : \d+ )? # The rest of the URL is optional, and begins with / . . . (?: /? # The rest are heuristics for what seems to work well [^.!,?;:"\'<>()\[\]{}\s\x7F-\xFF]* (?: [.!,?;:]+ [^.!,?;:"\'<>()\[\]{}\s\x7F-\xFF]+ #\'" )* )? )'; $this->punct = '[\!"\#\$%&\'()\*\+,\-\./:;<=>\?@\[\\\\\]\^_`{\|}\~]'; $this->valignre = '[\-^~]'; $this->tblalignre = '[<>=]'; $this->halignre = '(?:<>|[<>=])'; $this->alignre = '(?:(?:' . $this->valignre . '|<>' . $this->valignre . '?|' . $this->valignre . '?<>|' . $this->valignre . '?' . $this->halignre . '?|' . $this->halignre . '?' . $this->valignre . '?)(?!\w))'; $this->imgalignre = '(?:(?:[<>]|' . $this->valignre . '){1,2})'; $this->clstypadre = '(?: (?:\([A-Za-z0-9_\- \#]+\)) | (?:{ (?: \( [^)]+ \) | [^\}] )+ }) | (?:\(+? (?![A-Za-z0-9_\-\#]) ) | (?:\)+?) | (?: \[ [a-zA-Z\-]+? \] ) )'; $this->clstyre = '(?: (?:\([A-Za-z0-9_\- \#]+\)) | (?:{ [A-Za-z0-9_\-](?: \( [^)]+ \) | [^\}] )+ }) | (?: \[ [a-zA-Z\-]+? \] ) )'; $this->clstyfiltre = '(?: (?:\([A-Za-z0-9_\- \#]+\)) | (?:{ [A-Za-z0-9_\-](?: \( [^)]+ \) | [^\}] )+ }) | (?:\|[^\|]+\|) | (?:\(+?(?![A-Za-z0-9_\-\#])) | (?:\)+) | (?: \[ [a-zA-Z]+? \] ) )'; $this->codere = '(?: (?: [\[{] @ # opening (?:\[([A-Za-z0-9]+)\])? # $1: language id (.+?) # $2: code @ # closing [\]}] ) | (?: (?:^|(?<=[\s\(])) @ # opening (?:\[([A-Za-z0-9]+)\])? # $3: language id ([^\s].+?[^\s]) # $4: code itself @ # closing (?:$|(?=' . $this->punct . '{1,2}|\s)) ) )'; $this->blocktags = ' < (( /? ( h[1-6] | p | pre | div | table | t[rdh] | [ou]l | li | block(?:quote|code) | form | input | select | option | textarea ) [ >] ) | !-- ) '; } // function _create_re /** * Transforms the provided text using Textile markup rules. * * @param $str The @c string specifying the text to process. * * @return A @c string containing the processed (X)HTML. * * @public */ function process($str) { /* * Function names in PHP are case insensitive, so function * textile() cannot be redefined. Thus, this PHP implementation * will only use process(). * * return $this->textile($str); * } // function process * * function textile($str) { */ // quick translator for abbreviated block names // to their tag $macros = array('bq' => 'blockquote'); // an array to hold any portions of the text to be preserved // without further processing by Textile array_unshift($this->repl, array()); // strip out extra newline characters. we're only matching for \n herein //$str = preg_replace('!(?:\r?\n|\r)!', "\n", $str); $str = preg_replace('!(?:\015?\012|\015)!', "\n", $str); // optionally remove trailing spaces if ($this->options['trim_spaces']) { $str = preg_replace('/ +$/m', '', $str); } // preserve contents of the '==', 'pre', 'blockcode' sections $str = preg_replace_callback('{(^|\n\n)==(.+?)==($|\n\n)}s', $this->_cb('"$m[1]\n\n" . $this->_repl($this->repl[0], $this->format_block(array("text" => $m[2]))) . "\n\n$m[3]"'), $str); if (!$this->disable_html()) { // preserve style, script tag contents $str = preg_replace_callback('!(<(style|script)(?:>| .+?>).*?)!s', $this->_cb('$this->_repl($this->repl[0], $m[1])'), $str); // preserve HTML comments $str = preg_replace_callback('|()|s', $this->_cb('$this->_repl($this->repl[0], $m[1])'), $str); // preserve pre block contents, encode contents by default $pre_start = count($this->repl[0]); $str = preg_replace_callback('{(]*)?>)(.+?)()}s', $this->_cb('"\n\n" . $this->_repl($this->repl[0], $m[1] . $this->encode_html($m[2], 1) . $m[3]) . "\n\n"'), $str); // fix code tags within pre blocks we just saved. for ($i = $pre_start; $i < count($this->repl[0]); $i++) { $this->repl[0][$i] = preg_replace('|<(/?)code(.*?)>|s', '<$1code$2>', $this->repl[0][$i]); } // preserve code blocks by default, encode contents $str = preg_replace_callback('{(]+)?>)(.+?)()}s', $this->_cb('$this->_repl($this->repl[0], $m[1] . $this->encode_html($m[2], 1) . $m[3])'), $str); // encode blockcode tag (an XHTML 2 tag) and encode it's // content by default $str = preg_replace_callback('{(]+)?>)(.+?)()}s', $this->_cb('"\n\n" . $this->_repl($this->repl[0], $m[1] . $this->encode_html($m[2], 1) . $m[3]) . "\n\n"'), $str); // preserve PHPish, ASPish code $str = preg_replace_callback('!(<([\?%]).*?(\2)>)!s', $this->_cb('$this->_repl($this->repl[0], $m[1])'), $str); } // pass through and remove links that follow this format // [id_without_spaces (optional title text)]url // lines like this are stripped from the content, and can be // referred to using the "link text":id_without_spaces syntax //$links = array(); $str = preg_replace_callback('{(?:\n|^) [ ]* \[ ([^ ]+?) [ ]*? (?:\( (.+?) \) )? \] ((?:(?:ftp|https?|telnet|nntp)://|/)[^ ]+?) [ ]* (\n|$)}mx', $this->_cb('($this->links[$m[1]] = array("url" => $m[3], "title" => $m[2])) ? $m[4] : $m[4]'), $str); //$this->links = $links; // eliminate starting/ending blank lines $str = preg_replace('/^\n+/s', '', $str, 1); $str = preg_replace('/\n+$/s', '', $str, 1); // split up text into paragraph blocks, capturing newlines too $para = preg_split('/(\n{2,})/', $str, -1, PREG_SPLIT_DELIM_CAPTURE); unset($block, $bqlang, $filter, $class, $sticky, $lines, $style, $stickybuff, $lang, $clear); $out = ''; foreach ($para as $para) { if (preg_match('/^\n+$/s', $para)) { if ($sticky && $stickybuff) { $stickybuff .= $para; } else { $out .= $para; } continue; } if ($sticky) { $sticky++; } else { unset($block); unset($class); $style = ''; unset($lang); } unset($id, $cite, $align, $padleft, $padright, $lines, $buffer); if (preg_match('{^(h[1-6]|p|bq|bc|fn\d+) ((?:' . $this->clstyfiltre . '*|' . $this->halignre . ')*) (\.\.?) (?::(\d+|' . $this->urlre . '))?\ (.*)$}sx', $para, $matches)) { if ($sticky) { if ($block == 'bc') { // close our blockcode section $out = preg_replace('/\n\n$/', '', $out, 1); $out .= $this->options['_blockcode_close'] . "\n\n"; } elseif ($block == 'bq') { $out = preg_replace('/\n\n$/', '', $out, 1); $out .= '' . "\n\n"; } elseif ($block == 'table') { $table_out = $this->format_table(array('text' => $stickybuff)); if (!$table_out) { $table_out = ''; } $out .= $table_out; unset($stickybuff); } elseif ($block == 'dl') { $dl_out = $this->format_deflist(array('text' => $stickybuff)); if (!$dl_out) { $dl_out = ''; } $out .= $dl_out; unset($stickybuff); } $sticky = 0; } // block macros: h[1-6](class)., bq(class)., bc(class)., p(class). //warn "paragraph: [[$para]]\n\tblock: $1\n\tparams: $2\n\tcite: $4"; $block = $matches[1]; $params = $matches[2]; $cite = $matches[4]; if ($matches[3] == '..') { $sticky = 1; } else { $sticky = 0; unset($class); unset($bqlang); unset($lang); $style = ''; unset($filter); } if (preg_match('/^h([1-6])$/', $block, $matches2)) { if ($this->options['head_offset']) { $block = 'h' . ($matches2[1] + $this->options['head_offset']); } } if (preg_match('{(' . $this->halignre . '+)}', $params, $matches2)) { $align = $matches2[1]; $params = preg_replace('{' . $this->halignre . '+}', '', $params, 1); } if ($params) { if (preg_match('/\|(.+)\|/', $params, $matches2)) { $filter = $matches2[1]; $params = preg_replace('/\|.+?\|/', '', $params, 1); } if (preg_match('/{([^}]+)}/', $params, $matches2)) { $style = $matches2[1]; $style = preg_replace('/\n/', ' ', $style); $params = preg_replace('/{[^}]+}/', '', $params); } if (preg_match('/\(([A-Za-z0-9_\-\ ]+?)(?:\#(.+?))?\)/', $params, $matches2) || preg_match('/\(([A-Za-z0-9_\-\ ]+?)?(?:\#(.+?))\)/', $params, $matches2)) { if ($matches2[1] || $matches2[2]) { $class = $matches2[1]; $id = $matches2[2]; if ($class) { $params = preg_replace('/\([A-Za-z0-9_\-\ ]+?(#.*?)?\)/', '', $params); } elseif ($id) { $params = preg_replace('/\(#.+?\)/', '', $params); } } } if (preg_match('/(\(+)/', $params, $matches2)) { $padleft = strlen($matches2[1]); $params = preg_replace('/\(+/', '', $params, 1); } if (preg_match('/(\)+)/', $params, $matches2)) { $padright = strlen($matches2[1]); $params = preg_replace('/\)+/', '', $params, 1); } if (preg_match('/\[(.+?)\]/', $params, $matches2)) { $lang = $matches2[1]; if ($block == 'bc') { $bqlang = $lang; unset($lang); } $params = preg_replace('/\[.+?\]/', '', $params, 1); } } // 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"; $para = $matches[5]; } elseif (preg_match('|^$|', $para, $matches)) { $buffer = $this->repl[0][$matches[1] - 1]; } elseif (preg_match('/^clear([<>]+)?\.$/', $para, $matches)) { if ($matches[1] == '<') { $clear = 'left'; } elseif ($matches[1] == '>') { $clear = 'right'; } else { $clear = 'both'; } continue; } elseif ($sticky && $stickybuff && ($block == 'table' || $block == 'dl')) { $stickybuff .= $para; continue; } elseif (preg_match('{^(?:' . $this->halignre . '|' . $this->clstypadre . '*)* [\*\#] (?:' . $this->halignre . '|' . $this->clstypadre . '*)* \ }x', $para)) { // '*', '#' prefix means a list $buffer = $this->format_list(array('text' => $para)); } elseif (preg_match('{^(?:table(?:' . $this->tblalignre . '|' . $this->clstypadre . '*)* (\.\.?)\s+)? (?:_|' . $this->alignre . '|' . $this->clstypadre . '*)*\|}x', $para, $matches)) { // handle wiki-style tables if ($matches[1] && ($matches[1] == '..')) { $block = 'table'; $stickybuff = $para; $sticky = 1; continue; } else { $buffer = $this->format_table(array('text' => $para)); } } elseif (preg_match('{^(?:dl(?:' . $this->clstyre . ')*(\.\.?)\s+)}x', $para, $matches)) { // handle definition lists if ($matches[1] && ($matches[1] == '..')) { $block = 'dl'; $stickybuff = $para; $sticky = 1; continue; } else { $buffer = $this->format_deflist(array('text' => $para)); } } if ($buffer) { $out .= $buffer; continue; } $lines = preg_split('/\n/', $para); if ((count($lines) == 1) && ($lines[0] == '')) { continue; } $block = ($block ? $block : 'p'); $buffer = ''; $pre = ''; $post = ''; if ($block == 'bc') { if ($sticky <= 1) { $pre .= $this->options['_blockcode_open']; $pre = preg_replace('/>$/s', '', $pre, 1); if ($bqlang) { $pre .= " language=\"$bqlang\""; } if ($align) { $alignment = $this->_halign($align); if ($this->options['css_mode']) { if (($padleft || $padright) && (($alignment == 'left') || ($alignment == 'right'))) { $style .= ';float:' . $alignment; } else { $style .= ';text-align:' . $alignment; } $class .= ' ' . ($this->options['css']["class_align_$alignment"] ? $this->options['css']["class_align_$alignment"] : $alignment); } else { if ($alignment) { $pre .= " align=\"$alignment\""; } } } if ($padleft) { $style .= ";padding-left:${padleft}em"; } if ($padright) { $style .= ";padding-right:${padright}em"; } if ($clear) { $style .= ";clear:${clear}"; } if ($class) { $class = preg_replace('/^ /', '', $class, 1); } if ($class) { $pre .= " class=\"$class\""; } if ($id) { $pre .= " id=\"$id\""; } if ($style) { $style = preg_replace('/^;/', '', $style, 1); } if ($style) { $pre .= " style=\"$style\""; } if ($lang) { $pre .= " lang=\"$lang\""; } $pre .= '>'; unset($lang); unset($bqlang); unset($clear); } $para = preg_replace_callback('{(?:^|(?<=[\s>])|([{[])) ==(.+?)== (?:$|([\]}])|(?=' . $this->punct . '{1,2}|\s))}sx', $this->_cb('$this->_repl($this->repl[0], $this->format_block(array("text" => $m[2], "inline" => 1, "pre" => $m[1], "post" => $m[3])))'), $para); $buffer .= $this->encode_html_basic($para, 1); $buffer = preg_replace('/<textile#(\d+)>/', '', $buffer); if ($sticky == 0) { $post .= $this->options['_blockcode_close']; } $out .= $pre . $buffer . $post; continue; } elseif ($block == 'bq') { if ($sticky <= 1) { $pre .= '_halign($align); if ($this->options['css_mode']) { if (($padleft || $padright) && (($alignment == 'left') || ($alignment == 'right'))) { $style .= ';float:' . $alignment; } else { $style .= ';text-align:' . $alignment; } $class .= ' ' . ($this->options['css']["class_align_$alignment"] ? $this->options['css']["class_align_$alignment"] : $alignment); } else { if ($alignment) { $pre .= " align=\"$alignment\""; } } } if ($padleft) { $style .= ";padding-left:${padleft}em"; } if ($padright) { $style .= ";padding-right:${padright}em"; } if ($clear) { $style .= ";clear:${clear}"; } if ($class) { $class = preg_replace('/^ /', '', $class, 1); } if ($class) { $pre .= " class=\"$class\""; } if ($id) { $pre .= " id=\"$id\""; } if ($style) { $style = preg_replace('/^;/', '', $style, 1); } if ($style) { $pre .= " style=\"$style\""; } if ($lang) { $pre .= " lang=\"$lang\""; } if ($cite) { $pre .= ' cite="' . $this->format_url(array('url' => $cite)) . '"'; } $pre .= '>'; unset($clear); } $pre .= '

'; } elseif (preg_match('/fn(\d+)/', $block, $matches)) { $fnum = $matches[1]; $pre .= 'options['css']['class_footnote']) { $class .= ' ' . $this->options['css']['class_footnote']; } if ($align) { $alignment = $this->_halign($align); if ($this->options['css_mode']) { if (($padleft || $padright) && (($alignment == 'left') || ($alignment == 'right'))) { $style .= ';float:' . $alignment; } else { $style .= ';text-align:' . $alignment; } $class .= ($this->options['css']["class_align_$alignment"] ? $this->options['css']["class_align_$alignment"] : $alignment); } else { $pre .= " align=\"$alignment\""; } } if ($padleft) { $style .= ";padding-left:${padleft}em"; } if ($padright) { $style .= ";padding-right:${padright}em"; } if ($clear) { $style .= ";clear:${clear}"; } if ($class) { $class = preg_replace('/^ /', '', $class, 1); } if ($class) { $pre .= " class=\"$class\""; } $pre .= ' id="' . ($this->options['css']['id_footnote_prefix'] ? $this->options['css']['id_footnote_prefix'] : 'fn') . $fnum . '"'; if ($style) { $style = preg_replace('/^;/', '', $style, 1); } if ($style) { $pre .= " style=\"$style\""; } if ($lang) { $pre .= " lang=\"$lang\""; } $pre .= '>'; $pre .= '' . $fnum . ' '; // we can close like a regular paragraph tag now $block = 'p'; unset($clear); } else { $pre .= '<' . ($macros[$block] ? $macros[$block] : $block); if ($align) { $alignment = $this->_halign($align); if ($this->options['css_mode']) { if (($padleft || $padright) && (($alignment == 'left') || ($alignment == 'right'))) { $style .= ';float:' . $alignment; } else { $style .= ';text-align:' . $alignment; } $class .= ' ' . ($this->options['css']["class_align_$alignment"] ? $this->options['css']["class_align_$alignment"] : $alignment); } else { $pre .= " align=\"$alignment\""; } } if ($padleft) { $style .= ";padding-left:${padleft}em"; } if ($padright) { $style .= ";padding-right:${padright}em"; } if ($clear) { $style .= ";clear:${clear}"; } if ($class) { $class = preg_replace('/^ /', '', $class, 1); } if ($class) { $pre .= " class=\"$class\""; } if ($id) { $pre .= " id=\"$id\""; } if ($style) { $style = preg_replace('/^;/', '', $style, 1); } if ($style) { $pre .= " style=\"$style\""; } if ($lang) { $pre .= " lang=\"$lang\""; } if ($cite && ($block == 'bq')) { $pre .= ' cite="' . $this->format_url(array('url' => $cite)) . '"'; } $pre .= '>'; unset($clear); } $buffer = $this->format_paragraph(array('text' => $para)); if ($block == 'bq') { if (!preg_match('/]/', $buffer)) { $post .= '

'; } if ($sticky == 0) { $post .= ''; } } else { $post .= ''; } if (preg_match('{' . $this->blocktags . '}x', $buffer)) { $buffer = preg_replace('/^\n\n/s', '', $buffer, 1); $out .= $buffer; } else { if ($filter) { $buffer = $this->format_block(array('text' => "|$filter|" . $buffer, 'inline' => 1)); } $out .= $pre . $buffer . $post; } } if ($sticky) { if ($block == 'bc') { // close our blockcode section $out .= $this->options['_blockcode_close']; // . "\n\n"; } elseif ($block == 'bq') { $out .= ''; // . "\n\n"; } elseif (($block == 'table') && $stickybuff) { $table_out = $this->format_table(array('text' => $stickybuff)); if ($table_out) { $out .= $table_out; } } elseif (($block == 'dl') && $stickybuff) { $dl_out = $this->format_deflist(array('text' => $stickybuff)); if ($dl_out) { $out .= $dl_out; } } } // cleanup-- restore preserved blocks for ($i = count($this->repl[0]); $i > 0; $i--) { $out = preg_replace('!(?:<|<)textile#' . $i . '(?:>|>)!', $this->repl[0][$i - 1], $out, 1); } array_shift($this->repl); // scan for br, hr tags that are not closed and close them // only for xhtml! just the common ones -- don't fret over input // and the like. if (preg_match('/^xhtml/i', $this->flavor())) { $out = preg_replace('/(<(?:img|br|hr)[^>]*?(?/', '$1 />', $out); } return $out; } // function process /** * Processes a single paragraph. The following attributes are * allowed: * *
    * *
  • text * * The text to be processed.
  • * *
* * @param $args An @c array specifying the attributes for formatting * the paragraph. * * @return A @c string containing the formatted paragraph. * * @private */ function format_paragraph($args) { $buffer = ($args['text'] ? $args['text'] : ''); array_unshift($this->repl, array()); $buffer = preg_replace_callback('{(?:^|(?<=[\s>])|([{[])) ==(.+?)== (?:$|([\]}])|(?=' . $this->punct . '{1,2}|\s))}sx', $this->_cb('$this->_repl($this->repl[0], $this->format_block(array("text" => $m[2], "inline" => 1, "pre" => $m[1], "post" => $m[3])))'), $buffer); unset($tokens); if (preg_match('/disable_html())) { // optimization -- no point in tokenizing if we // have no tags to tokenize $tokens = $this->_tokenize($buffer); } else { $tokens = array(array('text', $buffer)); } $result = ''; foreach ($tokens as $token) { $text = $token[1]; if ($token[0] == 'tag') { $text = preg_replace('/&(?!amp;)/', '&', $text); $result .= $text; } else { $text = $this->format_inline(array('text' => $text)); $result .= $text; } } // now, add line breaks for lines that contain plaintext $lines = preg_split('/\n/', $result); $result = ''; $needs_closing = 0; foreach ($lines as $line) { if (!preg_match('{(' . $this->blocktags . ')}x', $line) && ((preg_match('/^[^<]/', $line) || preg_match('/>[^<]/', $line)) || !preg_match('/options['_line_open']) { if ($result != '') { $result .= "\n"; } $result .= $this->options['_line_open'] . $line . $this->options['_line_close']; } else { if ($needs_closing) { $result .= $this->options['_line_close'] . "\n"; } else { $needs_closing = 1; if ($result != '') { $result .= "\n"; } } $result .= $line; } } else { if ($needs_closing) { $result .= $this->options['_line_close'] . "\n"; } else { if ($result != '') { $result .= "\n"; } } $result .= $line; $needs_closing = 0; } } // at this point, we will restore the \001's to \n's (reversing // the step taken in _tokenize). //$result = preg_replace('/\r/', "\n", $result); $result = preg_replace('/\001/', "\n", $result); for ($i = count($this->repl[0]); $i > 0; $i--) { $result = preg_replace("||", $this->repl[0][$i - 1], $result, 1); } array_shift($this->repl); // quotalize if ($this->options['do_quotes']) { $result = $this->process_quotes($result); } return $result; } // function format_paragraph /** * Processes an inline string (plaintext) for Textile syntax. * The following attributes are allowed: * *
    * *
  • text * * The text to be processed.
  • * *
* * @param $args An @c array specifying the attributes for formatting * the inline string. * * @return A @c string containing the formatted inline string. * * @private */ function format_inline($args) { $qtags = array(array('**', 'b', '(?repl, array()); $text = preg_replace_callback('{' . $this->codere . '}mx', $this->_cb('$this->_repl($this->repl[0], $this->format_code(array("text" => $m[2] . $m[4], "lang" => $m[1] . $m[3])))'), $text); // images must be processed before encoding the text since they might // have the <, > alignment specifiers... // !blah (alt)! -> image $text = preg_replace_callback('{(?:^|(?<=[\s>])|([{[])) # $1: open brace/bracket ! # opening (' . $this->imgalignre . '?) # $2: optional alignment (' . $this->clstypadre . '*) # $3: optional CSS class/id (' . $this->imgalignre . '?) # $4: optional alignment (?:\s*) # space between alignment/css stuff ([^\s\(!]+) # $5: filename (\s*[^\(!]*(?:\([^\)]+\))?[^!]*) # $6: extras (alt text) ! # closing (?::(\d+|' . $this->urlre . '))? # $7: optional URL (?:$|([\]}])|(?=' . $this->punct . '{1,2}|\s)) # $8: closing brace/bracket }mx', $this->_cb('$this->_repl($this->repl[0], $this->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); $text = preg_replace_callback('{(?:^|(?<=[\s>])|([{[])) # $1: open brace/bracket % # opening (' . $this->halignre . '?) # $2: optional alignment (' . $this->clstyre . '*) # $3: optional CSS class/id (' . $this->halignre . '?) # $4: optional alignment (?:\s*) # spacing ([^%]+?) # $5: text % # closing (?::(\d+|' . $this->urlre . '))? # $6: optional URL (?:$|([]}])|(?=' . $this->punct . '{1,2}|\s)) # $7: closing brace/bracket }mx', $this->_cb('$this->_repl($this->repl[0], $this->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); $text = $this->encode_html($text); $text = preg_replace('!<textile#(\d+)>!', '', $text); $text = preg_replace('!&quot;!', '"', $text); $text = preg_replace('!&(([a-z]+|#\d+);)!', '&$1', $text); $text = preg_replace('!"!', '"', $text); // These create markup with entities. Do first and 'save' result for later: // "text":url -> hyperlink // links with brackets surrounding $parenre = '\( (?: [^()] )* \)'; $text = preg_replace_callback('{( [{[] (?: (?:" # quote character (' . $this->clstyre . '*)? # $2: optional CSS class/id ([^"]+?) # $3: link text (?:\( ( (?:[^()]|' . $parenre . ')*) \))? # $4: optional link title " # closing quote ) | (?:\' # open single quote (' . $this->clstyre . '*)? # $5: optional CSS class/id ([^\']+?) # $6: link text (?:\( ( (?:[^()]|' . $parenre . ')*) \))? # $7: optional link title \' # closing quote ) ) :(.+?) # $8: URL suffix [\]}] ) }mx', $this->_cb('$this->_repl($this->repl[0], $this->format_link(array("text" => $m[1], "linktext" => $m[3] . $m[6], "title" => $this->encode_html_basic($m[4] . $m[7]), "url" => $m[8], "clsty" => $m[2] . $m[5])))'), $text); $text = preg_replace_callback('{((?:^|(?<=[\s>\(])) # $1: open brace/bracket (?: (?:" # quote character " (' . $this->clstyre . '*)? # $2: optional CSS class/id ([^"]+?) # $3: link text " (?:\( ( (?:[^()]|' . $parenre . ')*) \))? # $4: optional link title " # closing quote # " ) | (?:\' # open single quote \' (' . $this->clstyre . '*)? # $5: optional CSS class/id ([^\']+?) # $6: link text \' (?:\( ( (?:[^()]|' . $parenre . ')*) \))? # $7: optional link title \' # closing quote \' ) ) :(\d+|' . $this->urlre . ') # $8: URL suffix (?:$|(?=' . $this->punct . '{1,2}|\s))) # $9: closing brace/bracket }mx', $this->_cb('$this->_repl($this->repl[0], $this->format_link(array("text" => $m[1], "linktext" => $m[3] . $m[6], "title" => $this->encode_html_basic($m[4] . $m[7]), "url" => $m[8], "clsty" => $m[2] . $m[5])))'), $text); if (preg_match('/^xhtml2/', $this->flavor())) { // citation with cite link $text = preg_replace_callback('{(?:^|(?<=[\s>\'"\(])|([{[])) # $1: open brace/bracket \' \?\? # opening \'??\' ([^\?]+?) # $2: characters (can\'t contain \'?\') \?\? # closing \'??\' :(\d+|' . $this->urlre . ') # $3: optional citation URL (?:$|([\]}])|(?=' . $this->punct . '{1,2}|\s)) # $4: closing brace/bracket }mx', $this->_cb('$this->_repl($this->repl[0], $this->format_cite(array("pre" => $m[1], "text" => $m[2], "cite" => $m[3], "post" => $m[4])))'), $text); } // footnotes if (preg_match('/[^ ]\[\d+\]/', $text)) { $fntag = 'options['css']['class_footnote']) { $fntag .= ' class="' . $this->options['css']['class_footnote'] . '"'; } $fntag .= '>$2', $text); } // translate macros: $text = preg_replace_callback('{(\{)(.+?)(\})}x', $this->_cb('$this->format_macro(array("pre" => $m[1], "post" => $m[3], "macro" => $m[2]))'), $text); // these were present with textile 1 and are common enough // to not require macro braces... // (tm) -> ™ $text = preg_replace('|[\(\[]TM[\)\]]|i', '™', $text); // (c) -> © $text = preg_replace('|[\(\[]C[\)\]]|i', '©', $text); // (r) -> ® $text = preg_replace('|[\(\[]R[\)\]]|i', '®', $text); if ($this->preserve_spaces()) { // replace two spaces with an em space $text = preg_replace('/(?tmp['f'][], $this->tmp['r'][], $qf, $cls) = $tag; if ($last != ($text = preg_replace_callback('{(?:^|(?<=[\s>\'"])|([{[])) # "\' $1 - pre ' . $qf . ' # (?:(' . $this->clstyre . '*))? # $2 - attributes ([^' . $cls . '\s].*?) # $3 - content (?<=\S)' . $qf . ' # (?:$|([\]}])|(?=' . $this->punct . '{1,2}|\s)) # $4 - post }mx', $this->_cb('$this->format_tag(array("tag" => end($this->tmp["r"]), "marker" => end($this->tmp["f"]), "pre" => $m[1], "text" => $m[3], "clsty" => $m[2], "post" => $m[4]))'), $text))) { $redo = ($redo || ($last != $text)); $last = $text; } array_pop($this->tmp['f']); array_pop($this->tmp['r']); } } // superscript is an even simpler replacement... $text = preg_replace('/(?$1', $text); // ABC(Aye Bee Cee) -> acronym $text = preg_replace_callback('{\b([A-Z][A-Za-z0-9]*?[A-Z0-9]+?)\b(?:[(]([^)]*)[)])}', $this->_cb('$this->_repl($this->repl[0],"encode_html_basic($m[2]) . "\">$m[1]")'), $text); // ABC -> 'capped' span if ($this->tmp['caps'][] = $this->options['css']['class_caps']) { $text = preg_replace_callback('/(^|[^"][>\s]) # " ((?:[A-Z](?:[A-Z0-9\.,\']|\&){2,}\ *)+?) # \' (?=[^A-Z\.0-9]|$) /mx', $this->_cb('$m[1] . $this->_repl($this->repl[0], "tmp[caps]) . "\">$m[2]")'), $text); } array_pop($this->tmp['caps']); // nxn -> n×n $text = preg_replace('!((?:[0-9\.]0|[1-9]|\d[\'"])\ ?)x(\ ?\d)!', '$1×$2', $text); // translate these entities to the Unicode equivalents: $text = preg_replace('/…/', '…', $text); $text = preg_replace('/‘/', '‘', $text); $text = preg_replace('/’/', '’', $text); $text = preg_replace('/“/', '“', $text); $text = preg_replace('/”/', '”', $text); $text = preg_replace('/–/', '–', $text); $text = preg_replace('/—/', '—', $text); // Restore replacements done earlier: for ($i = count($this->repl[0]); $i > 0; $i--) { $text = preg_replace("||", $this->repl[0][$i - 1], $text); } array_shift($this->repl); // translate entities to characters for highbit stuff since // we're using utf8 // removed for backward compatability with older versions of Perl //if (preg_match('/^utf-?8$/i', $this->options['charset'])) { // // translate any unicode entities to native UTF-8 // $text = preg_replace('/\&\#(\d+);/e', '($1 > 127) ? pack('U', $1) : chr($1)', $text); //} return $text; } // function format_inline /** * Responsible for processing a particular macro. Arguments passed * include: * *
    * *
  • pre * * open brace character
  • * *
  • post * * close brace character
  • * *
  • macro * * the macro to be executed
  • * *
* * The return value from this method would be the replacement * text for the macro given. If the macro is not defined, it will * return pre + macro + post, thereby preserving the original * macro string. * * @param $attrs An @c array containing the attributes for * formatting the macro. * * @return A @c string containing the formatted macro. * * @private */ function format_macro($attrs) { $macro = $attrs['macro']; if ($this->options['macros'][$macro]) { return $this->options['macros'][$macro]; } return $attrs['pre'] . $macro . $attrs['post']; } // function format_macro /** * Processes text for a citation tag. The following attributes * are allowed: * *
    * *
  • pre * * Any text that comes before the citation.
  • * *
  • text * * The text that is being cited.
  • * *
  • cite * * The URL of the citation.
  • * *
  • post * * Any text that follows the citation.
  • * *
* * @param $args An @c array specifying the attributes for formatting * the citation. * * @return A @c string containing the formatted citation. * * @private */ function format_cite($args) { $pre = ($args['pre'] ? $args['pre'] : ''); $text = ($args['text'] ? $args['text'] : ''); $cite = $args['cite']; $post = ($args['post'] ? $args['post'] : ''); $this->_strip_borders($pre, $post); $tag = $pre . 'flavor()) && $cite) { $cite = $this->format_url(array('url' => $cite)); $tag .= " cite=\"$cite\""; } else { $post .= ':'; } $tag .= '>'; return $tag . $this->format_inline(array('text' => $text)) . '' . $post; } // function format_cite /** * Processes '@...@' type blocks (code snippets). The following * attributes are allowed: * *
    * *
  • text * * The text of the code itself.
  • * *
  • lang * * The language (programming language) for the code.
  • * *
* * @param $args An @c array specifying the attributes for formatting * the code. * * @return A @c string containing the formatted code. * * @private */ function format_code($args) { $code = ($args['text'] ? $args['text'] : ''); $lang = $args['lang']; $code = $this->encode_html($code, 1); $code = preg_replace('/<textile#(\d+)>/', '', $code); $tag = '' . $code . ''; } // function format_code /** * Returns a string of tag attributes to accomodate the class, * style and symbols present in @c $clsty. * * @c $clsty is checked for: * *
    * *
  • {...} * * style rules. If present, they are appended to $style.
  • * *
  • (...#...) * * class and/or ID name declaration
  • * *
  • ( (one or more) * * pad left characters
  • * *
  • ) (one or more) * * pad right characters
  • * *
  • [ll] * * language declaration
  • * *
* * The attribute string returned will contain any combination * of class, id, style and/or lang attributes. * * @param $clsty A @c string specifying the class/style to process. * @param $class A @c string specifying the predetermined class. * @param $style A @c string specifying the predetermined style. * * @return A @c string containing the formatted class, ID, style, * and/or language. * * @private */ function format_classstyle($clsty = NULL, $class = NULL, $style = NULL) { $class = preg_replace('/^ /', '', $class, 1); unset($lang, $padleft, $padright, $id); if ($clsty && preg_match('/{([^}]+)}/', $clsty, $matches)) { $_style = $matches[1]; $_style = preg_replace('/\n/', ' ', $_style); $style .= ';' . $_style; $clsty = preg_replace('/{[^}]+}/', '', $clsty); } if ($clsty && (preg_match('/\(([A-Za-z0-9_\- ]+?)(?:#(.+?))?\)/', $clsty, $matches) || preg_match('/\(([A-Za-z0-9_\- ]+?)?(?:#(.+?))\)/', $clsty, $matches))) { if ($matches[1] || $matches[2]) { if ($class) { $class = $matches[1] . ' ' . $class; } else { $class = $matches[1]; } $id = $matches[2]; if ($class) { $clsty = preg_replace('/\([A-Za-z0-9_\- ]+?(#.*?)?\)/', '', $clsty); } if ($id) { $clsty = preg_replace('/\(#.+?\)/', '', $clsty); } } } if ($clsty && preg_match('/(\(+)/', $clsty, $matches)) { $padleft = strlen($matches[1]); $clsty = preg_replace('/\(+/', '', $clsty, 1); } if ($clsty && preg_match('/(\)+)/', $clsty, $matches)) { $padright = strlen($matches[1]); $clsty = preg_replace('/\)+/', '', $clsty, 1); } if ($clsty && preg_match('/\[(.+?)\]/', $clsty, $matches)) { $lang = $matches[1]; $clsty = preg_replace('/\[.+?\]/', '', $clsty); } $attrs = ''; if ($padleft) { $style .= ";padding-left:${padleft}em"; } if ($padright) { $style .= ";padding-right:${padright}em"; } $style = preg_replace('/^;/', '', $style, 1); $class = preg_replace('/^ /', '', $class, 1); $class = preg_replace('/ $/', '', $class, 1); if ($class) { $attrs .= " class=\"$class\""; } if ($id) { $attrs .= " id=\"$id\""; } if ($style) { $attrs .= " style=\"$style\""; } if ($lang) { $attrs .= " lang=\"$lang\""; } $attrs = preg_replace('/^ /', '', $attrs, 1); return $attrs; } // function format_classstyle /** * Constructs an HTML tag. Accepted arguments: * *
    * *
  • tag * * the tag to produce
  • * *
  • text * * the text to output inside the tag
  • * *
  • pre * * text to produce before the tag
  • * *
  • post * * text to produce following the tag
  • * *
  • clsty * * class and/or style attributes that should be assigned to the tag.
  • * *
* * @param $args @c array specifying the attributes for formatting * the tag. * * @return A @c string containing the formatted tag. * * @private */ function format_tag($args) { $tagname = $args['tag']; $text = ($args['text'] ? $args['text'] : ''); $pre = ($args['pre'] ? $args['pre'] : ''); $post = ($args['post'] ? $args['post'] : ''); $clsty = ($args['clsty'] ? $args['clsty'] : ''); $this->_strip_borders($pre, $post); $tag = "<$tagname"; $attr = $this->format_classstyle($clsty); if ($attr) { $tag .= " $attr"; } $tag .= ">$text"; return $pre . $tag . $post; } // function format_tag /** * Takes a Textile formatted definition list and * returns the markup for it. Arguments accepted: * *
    * *
  • text * * The text to be processed.
  • * *
* * @param $args An @c array specifying the attributes for formatting * the definition list. * * @return A @c string containing the formatted definition list. * * @private */ function format_deflist($args) { $str = ($args['text'] ? $args['text'] : ''); unset($clsty); $lines = preg_split('/\n/', $str); if (preg_match('{^(dl(' . $this->clstyre . '*?)\.\.?(?:\ +|$))}x', $lines[0], $matches)) { $clsty = $matches[2]; $lines[0] = substr($lines[0], strlen($matches[1])); } unset($dt, $dd); $out = ''; foreach ($lines as $line) { if (preg_match('{^((?:' . $this->clstyre . '*)(?:[^\ ].*?)(?add_term($dt, $dd); } $dt = $matches[1]; $dd = $matches[2]; } else { $dd .= "\n" . $line; } } if ($dt && $dd) { $out .= $this->add_term($dt, $dd); } $tag = 'format_classstyle($clsty); } if ($attr) { $tag .= " $attr"; } $tag .= '>' . "\n"; return $tag . $out . "\n"; } // function format_deflist /** * Processes a single definition list item from the provided term * and definition. * * @param $dt A @c string specifying the term to be defined. * @param $dd A @c string specifying the definition for the term. * * @return A @c string containing the formatted definition list * item. * * @private */ function add_term($dt, $dd) { unset($dtattr, $ddattr); unset($dtlang); if (preg_match('{^(' . $this->clstyre . '*)}x', $dt, $matches)) { $param = $matches[1]; $dtattr = $this->format_classstyle($param); if (preg_match('/\[([A-Za-z]+?)\]/', $param, $matches)) { $dtlang = $matches[1]; } $dt = substr($dt, strlen($param)); } if (preg_match('{^(' . $this->clstyre . '*)}x', $dd, $matches)) { $param = $matches[1]; // if the language was specified for the term, // then apply it to the definition as well (unless // already specified of course) if ($dtlang && preg_match('/\[([A-Za-z]+?)\]/', $param)) { unset($dtlang); } $ddattr = $this->format_classstyle(($dtlang ? "[$dtlang]" : '') . $param); $dd = substr($dd, strlen($param)); } $out = 'format_inline(array('text' => $dt)) . '' . "\n"; if (preg_match('/\n\n/', $dd)) { if (preg_match('/\n\n/', $dd)) { $dd = $this->process($dd); } } else { $dd = $this->format_paragraph(array('text' => $dd)); } $out .= '' . "\n"; return $out; } // function add_term /** * Takes a Textile formatted list (numeric or bulleted) and * returns the markup for it. Text that is passed in requires * substantial parsing, so the @c format_list method is a little * involved. But it should always produce a proper ordered * or unordered list. If it cannot (due to misbalanced input), * it will return the original text. Arguments accepted: * *
    * *
  • text * * The text to be processed.
  • * *
* * @param $args An @c array specifying the attributes for formatting * the list. * * @return A @c string containing the formatted list. * * @private */ function format_list($args) { $str = ($args['text'] ? $args['text'] : ''); $list_tags = array('*' => 'ul', '#' => 'ol'); $lines = preg_split('/\n/', $str); unset($stack); $last_depth = 0; $item = ''; $out = ''; foreach ($lines as $line) { if (preg_match('{^((?:' . $this->clstypadre . '*|' . $this->halignre . ')*) ([\#\*]+) ((?:' . $this->halignre . '|' . $this->clstypadre . '*)*) \ (.+)$}x', $line, $matches)) { if ($item != '') { if (preg_match('/\n/', $item)) { if ($this->options['_line_open']) { $item = preg_replace('/(]*>|^)/m', '$1' . $this->options['_line_open'], $item); $item = preg_replace('/(\n|$)/s', $this->options['_line_close'] . '$1', $item); } else { $item = preg_replace('/(\n)/s', $this->options['_line_close'] . '$1', $item); } } $out .= $item; $item = ''; } $type = substr($matches[2], 0, 1); $depth = strlen($matches[2]); $blockparam = $matches[1]; $itemparam = $matches[3]; $line = $matches[4]; unset ($blockclsty, $blockalign, $blockattr, $itemattr, $itemclsty, $itemalign); if (preg_match('{(' . $this->clstypadre . '+)}x', $blockparam, $matches)) { $blockclsty = $matches[1]; } if (preg_match('{(' . $this->halignre . '+)}', $blockparam, $matches)) { $blockalign = $matches[1]; } if (preg_match('{(' . $this->clstypadre . '+)}x', $itemparam, $matches)) { $itemclsty = $matches[1]; } if (preg_match('{(' . $this->halignre . '+)}', $itemparam, $matches)) { $itemalign = $matches[1]; } if ($itemclsty) { $itemattr = $this->format_classstyle($itemclsty); } if ($depth > $last_depth) { for ($j = $last_depth; $j < $depth; $j++) { $out .= "\n<$list_tags[$type]"; $stack[] = $type; if ($blockclsty) { $blockattr = $this->format_classstyle($blockclsty); if ($blockattr) { $out .= ' ' . $blockattr; } } $out .= ">\nformat_paragraph(array('text' => $line)); } if (preg_match('/\n/', $item, $matches)) { if ($this->options['_line_open']) { $item = preg_replace('/(]*>|^)/m', '$1' . $this->options['_line_open'], $item); $item = preg_replace('/(\n|$)/s', $this->options['_line_close'] . '$1', $item); } else { $item = preg_replace('/(\n)/s', $this->options['_line_close'] . '$1', $item); } } $out .= $item; for ($j = 1; $j <= $last_depth; $j++) { if ($j == 1) { $out .= ''; } $type = array_pop($stack); $out .= "\n" . '' . "\n"; if ($j != $last_depth) { $out .= ''; } } return $out . "\n"; } // function format_list /** * Processes '==xxxxx==' type blocks for filters. A filter * would follow the open '==' sequence and is specified within * pipe characters, like so: *
   *     ==|filter|text to be filtered==
   * 
* You may specify multiple filters in the filter portion of * the string. Simply comma delimit the filters you desire * to execute. Filters are defined using the filters method. * * @param $args An @c array specifying the attributes for formatting * the block. * * @return A @c string containing the formatted block. * * @private */ function format_block($args) { $str = ($args['text'] ? $args['text'] : ''); $inline = $args['inline']; $pre = ($args['pre'] ? $args['pre'] : ''); $post = ($args['post'] ? $args['post'] : ''); $this->_strip_borders($pre, $post); $filters = (preg_match('/^(\|(?:(?:[a-z0-9_\-]+)\|)+)/', $str, $matches) ? $matches[1] : ''); if ($filters) { $filtreg = preg_replace('/[^A-Za-z0-9]/', '\\\\$1', $filters); $str = preg_replace('/^' . $filtreg . '/', '', $str, 1); $filters = preg_replace('/^\|/', '', $filters, 1); $filters = preg_replace('/\|$/', '', $filter, 1); $filters = preg_split('/\|/', $filters); $str = $this->apply_filters(array('text' => $str, 'filters' => $filters)); $count = count($filters); if ($str = preg_replace('!(

){' . $count . '}!se', '(++$i ? "$1" : "$1")', $str) && $i) { $str = preg_replace('!(

){' . $count . '}!s', '$1', $str); $str = preg_replace('!(){' . $count . '}!s', '$1', $str); } } if ($inline) { // strip off opening para, closing para, since we're // operating within an inline block $str = preg_replace('/^\s*]*>/', '', $str, 1); $str = preg_replace('/<\/p>\s*$/', '', $str, 1); } return $pre . $str . $post; } // function format_block /** * Takes the Textile link attributes and transforms them into * a hyperlink. * * @param $args An @c array specifying the attributes for formatting * the link. * * @return A @c string containing the formatted link. * * @private */ function format_link($args) { $text = ($args['text'] ? $args['text'] : ''); $linktext = ($args['linktext'] ? $args['linktext'] : ''); $title = $args['title']; $url = $args['url']; $clsty = $args['clsty']; if (!$url || ($url == '')) { return $text; } if ($this->links && $this->links[$url]) { $title = ($title ? $title : $this->links[$url]['title']); $url = $this->links[$url]['url']; } $linktext = preg_replace('/ +$/', '', $linktext, 1); $linktext = $this->format_paragraph(array('text' => $linktext)); $url = $this->format_url(array('linktext' => $linktext, 'url' => $url)); $tag = "format_classstyle($clsty); if ($attr) { $tag .= " $attr"; } if ($title) { $title = preg_replace('/^\s+/', '', $title, 1); if (strlen($title)) { $tag .= " title=\"$title\""; } } $tag .= ">$linktext"; return $tag; } // function format_link /** * Takes the given @c $url and transforms it appropriately. * * @param $args An @c array specifying the attributes for formatting * the url. * * @return A @c string containing the formatted url. * * @private */ function format_url($args) { $url = ($args['url'] ? $args['url'] : ''); if (preg_match('/^(mailto:)?([-\+\w]+@[-\w]+(\.\w[-\w]*)+)$/', $url, $matches)) { $url = 'mailto:' . $this->mail_encode($matches[2]); } if (!preg_match('!^(/|\./|\.\./|#)!', $url)) { if (!preg_match('!^(https?|ftp|mailto|nntp|telnet)!', $url)) { $url = "http://$url"; } } $url = preg_replace('/&(?!amp;)/', '&', $url); $url = preg_replace('/\ /', '+', $url); $url = preg_replace_callback('/^((?:.+?)\?)(.+)$/', $this->_cb('$m[1] . $this->encode_url($m[2])'), $url); return $url; } // function format_url /** * Takes a Textile formatted span and returns the markup for it. * * @return A @c string containing the formatted span. * * @private */ function format_span($args) { $text = ($args['text'] ? $args['text'] : ''); $pre = ($args['pre'] ? $args['pre'] : ''); $post = ($args['post'] ? $args['post'] : ''); $align = $args['align']; $cite = ($args['cite'] ? $args['cite'] : ''); $clsty = $args['clsty']; $this->_strip_borders($pre, $post); unset($class, $style); $tag = "options['css_mode']) { $alignment = $this->_halign($align); if ($alignment) { $style .= ";float:$alignment"; } if ($alignment) { $class .= ' ' . $this->options['css']["class_align_$alignment"]; } } else { $alignment = ($this->_halign($align) ? $this->_halign($align) : $this->_valign($align)); if ($alignment) { $tag .= " align=\"$alignment\""; } } } $attr = $this->format_classstyle($clsty, $class, $style); if ($attr) { $tag .= " $attr"; } if ($cite) { $cite = preg_replace('/^:/', '', $cite, 1); $cite = $this->format_url(array('url' => $cite)); $tag .= " cite=\"$cite\""; } return $pre . $tag . '>' . $this->format_paragraph(array('text' => $text)) . '' . $post; } // function format_span /** * Returns markup for the given image. @c $src is the location of * the image, @c $extra contains the optional height/width and/or * alt text. @c $url is an optional hyperlink for the image. @c $class * holds the optional CSS class attribute. * * Arguments you may pass: * *
    * *
  • src * * The 'src' (URL) for the image. This may be a local path, * ideally starting with a '/'. Images can be located within * the file system if the docroot method is used to specify * where the docroot resides. If the image can be found, the * image_size method is used to determine the dimensions of * the image.
  • * *
  • extra * * Additional parameters for the image. This would include * alt text, height/width specification or scaling instructions.
  • * *
  • align * * Alignment attribute.
  • * *
  • pre * * Text to produce prior to the tag.
  • * *
  • post * * Text to produce following the tag.
  • * *
  • link * * Optional URL to connect with the image tag.
  • * *
  • clsty * * Class and/or style attributes.
  • * *
* * @param $args An @c array specifying the attributes for formatting * the image. * * @return A @c string containing the formatted image. * * @private */ function format_image($args) { $src = ($args['src'] ? $args['src'] : ''); $extra = $args['extra']; $align = $args['align']; $pre = ($args['pre'] ? $args['pre'] : ''); $post = ($args['post'] ? $args['post'] : ''); $link = $args['url']; $clsty = $args['clsty']; $this->_strip_borders($pre, $post); if (strlen($src) == 0) { return $pre . '!!' . $post; } unset($tag); if (preg_match('/^xhtml2/', $this->options['flavor'])) { unset($type); // poor man's mime typing. need to extend this externally if (preg_match('/(?:\.jpeg|\.jpg)$/i', $src)) { $type = 'image/jpeg'; } elseif (preg_match('/\.gif$/i', $src)) { $type = 'image/gif'; } elseif (preg_match('/\.png$/i', $src)) { $type = 'image/png'; } elseif (preg_match('/\.tiff$/i', $src)) { $type = 'image/tiff'; } $tag = "options['css_mode']) { $alignment = $this->_halign($align); if ($alignment) { $style .= ";float:$alignment"; } if ($alignment) { $class .= ' ' . $alignment; } $alignment = $this->_valign($align); if ($alignment) { $imgvalign = (preg_match('/(top|bottom)/', $alignment) ? 'text-' . $alignment : $alignment); if ($imgvalign) { $style .= ";vertical-align:$imgvalign"; } if ($alignment) { $class .= ' ' . $this->options['css']["class_align_$alignment"]; } } } else { $alignment = ($this->_halign($align) ? $this->_halign($align) : $this->_valign($align)); if ($alignment) { $tag .= " align=\"$alignment\""; } } } unset($pctw, $pcth, $w, $h, $alt); if ($extra) { $alt = (preg_match('/\(([^\)]+)\)/', $extra, $matches) ? $matches[1] : ''); $extra = preg_replace('/\([^\)]+\)/', '', $extra, 1); $pct = (preg_match('/(^|\s)(\d+)%(\s|$)/', $extra, $matches) ? $matches[2] : ''); if (!$pct) { list($pctw, $pcth) = (preg_match('/(^|\s)(\d+)%x(\d+)%(\s|$)/', $extra, $matches) ? array($matches[2], $matches[3]) : NULL); } else { $pctw = $pcth = $pct; } if (!$pctw && !$pcth) { list($w,$h) = (preg_match('/(^|\s)(\d+|\*)x(\d+|\*)(\s|$)/', $extra, $matches) ? array($matches[2], $matches[3]) : NULL); if ($w == '*') { $w = ''; } if ($h == '*') { $h = ''; } if (!$w) { $w = (preg_match('/(^|[,\s])(\d+)w([\s,]|$)/', $extra, $matches) ? $matches[2] : ''); } if (!$h) { $h = (preg_match('/(^|[,\s])(\d+)h([\s,]|$)/', $extra, $matches) ? $matches[2] : ''); } } } $alt = ($alt ? $alt : ''); if (!preg_match('/^xhtml2/', $this->options['flavor'])) { $tag .= ' alt="' . $this->encode_html_basic($alt) . '"'; } if ($w && $h) { if (!preg_match('/^xhtml2/', $this->options['flavor'])) { $tag .= " height=\"$h\" width=\"$w\""; } else { $style .= ";height:${h}px;width:${w}px"; } } else { list($image_w, $image_h) = $this->image_size($src); if (($image_w && $image_h) && ($w || $h)) { // image size determined, but only width or height specified if ($w && !$h) { // width defined, scale down height proportionately $h = intval($image_h * ($w / $image_w)); } elseif ($h && !$w) { $w = intval($image_w * ($h / $image_h)); } } else { $w = $image_w; $h = $image_h; } if ($w && $h) { if ($pctw || $pcth) { $w = intval($w * $pctw / 100); $h = intval($h * $pcth / 100); } if (!preg_match('/^xhtml2/', $this->options['flavor'])) { $tag .= " height=\"$h\" width=\"$w\""; } else { $style .= ";height:{$h}px;width:{$w}px"; } } } $attr = $this->format_classstyle($clsty, $class, $style); if ($attr) { $tag .= " $attr"; } if (preg_match('/^xhtml2/', $this->options['flavor'])) { $tag .= '>

' . $this->encode_html_basic($alt) . '

'; } elseif (preg_match('/^xhtml/', $this->options['flavor'])) { $tag .= ' />'; } else { $tag .= '>'; } if ($link) { $link = preg_replace('/^:/', '', $link, 1); $link = $this->format_url(array('url' => $link)); $tag = '' . $tag . ''; } return $pre . $tag . $post; } // function format_image /** * Takes a Wiki-ish string of data and transforms it into a full * table. * * @param $args An @c array specifying the attributes for formatting * the table. * * @return A @c string containing the formatted table. * * @private */ function format_table($args) { $str = ($args['text'] ? $args['text'] : ''); $lines = preg_split('/\n/', $str); unset($rows); $line_count = count($lines); for ($i = 0; $i < $line_count; $i++) { if (!preg_match('/\|\s*$/', $lines[$i])) { if ($i + 1 < $line_count) { if ($i + 1 <= count($lines) - 1) { $lines[$i + 1] = $lines[$i] . "\n" . $lines[$i + 1]; } } else { $rows[] = $lines[$i]; } } else { $rows[] = $lines[$i]; } } unset($tid, $tpadl, $tpadr, $tlang); $tclass = ''; $tstyle = ''; $talign = ''; if (preg_match('/^table[^\.]/', $rows[0])) { $row = $rows[0]; $row = preg_replace('/^table/', '', $row, 1); $params = 1; // process row parameters until none are left while ($params) { if (preg_match('{^(' . $this->tblalignre . ')}', $row, $matches)) { // found row alignment $talign .= $matches[1]; if ($matches[1]) { $row = substr($row, strlen($matches[1])); } if ($matches[1]) { continue; } } if (preg_match('{^(' . $this->clstypadre . ')}x', $row, $matches)) { // found a class/id/style/padding indicator $clsty = $matches[1]; if ($clsty) { $row = substr($row, strlen($clsty)); } if (preg_match('/{([^}]+)}/', $clsty, $matches)) { $tstyle = $matches[1]; $clsty = preg_replace('/{([^}]+)}/', '', $clsty, 1); if ($tstyle) { continue; } } if (preg_match('/\(([A-Za-z0-9_\- ]+?)(?:#(.+?))?\)/', $clsty, $matches) || preg_match('/\(([A-Za-z0-9_\- ]+?)?(?:#(.+?))\)/', $clsty, $matches)) { if ($matches[1] || $matches[2]) { $tclass = $matches[1]; $tid = $matches[2]; continue; } } if (preg_match('/(\(+)/', $clsty, $matches)) { $tpadl = strlen($matches[1]); } if (preg_match('/(\)+)/', $clsty, $matches)) { $tpadr = strlen($matches[1]); } if (preg_match('/\[(.+?)\]/', $clsty, $matches)) { $tlang = $matches[1]; } if ($clsty) { continue; } } $params = 0; } $row = preg_replace('/\.\s+/', '', $row, 1); $rows[0] = $row; } $out = ''; $cols = preg_split('/\|/', $rows[0] . ' '); unset($colaligns, $rowspans); foreach ($rows as $row) { $cols = preg_split('/\|/', $row . ' '); $colcount = count($cols) - 1; array_pop($cols); $colspan = 0; $row_out = ''; unset($rowclass, $rowid, $rowalign, $rowstyle, $rowheader); if (!$cols[0]) { $cols[0] = ''; } if (preg_match('/_/', $cols[0])) { $cols[0] = preg_replace('/_/', '', $cols[0]); $rowheader = 1; } if (preg_match('/{([^}]+)}/', $cols[0], $matches)) { $rowstyle = $matches[1]; $cols[0] = preg_replace('/{[^}]+}/', '', $cols[0]); } if (preg_match('/\(([^\#]+?)?(#(.+))?\)/', $cols[0], $matches)) { $rowclass = $matches[1]; $rowid = $matches[3]; $cols[0] = preg_replace('/\([^\)]+\)/', '', $cols[0]); } if (preg_match('{(' . $this->alignre . ')}', $cols[0], $matches)) { $rowalign = $matches[1]; } for ($c = $colcount - 1; $c > 0; $c--) { if ($rowspans[$c]) { $rowspans[$c]--; if ($rowspans[$c] > 1) { continue; } } unset($colclass, $colid, $header, $colparams, $colpadl, $colpadr, $collang); $colstyle = ''; $colalign = $colaligns[$c]; $col = array_pop($cols); $col = ($col ? $col : ''); $attrs = ''; if (preg_match('{^(((_|[/\\\\]\d+|' . $this->alignre . '|' . $this->clstypadre . ')+)\.\ )}x', $col, $matches)) { $colparams = $matches[2]; $col = substr($col, strlen($matches[1])); $params = 1; // keep processing column parameters until there // are none left... while ($params) { if (preg_match('{^(_|' . $this->alignre . ')(.*)$}', $colparams, $matches)) { // found alignment or heading indicator $attrs .= $matches[1]; if ($matches[1]) { $colparams = $matches[2]; } if ($matches[1]) { continue; } } if (preg_match('{^(' . $this->clstypadre . ')(.*)$}x', $colparams, $matches)) { // found a class/id/style/padding marker $clsty = $matches[1]; if ($clsty) { $colparams = $matches[2]; } if (preg_match('/{([^}]+)}/', $clsty, $matches)) { $colstyle = $matches[1]; $clsty = preg_replace('/{([^}]+)}/', '', $clsty, 1); } if (preg_match('/\(([A-Za-z0-9_\- ]+?)(?:#(.+?))?\)/', $clsty, $matches) || preg_match('/\(([A-Za-z0-9_\- ]+?)?(?:#(.+?))\)/', $clsty, $matches)) { if ($matches[1] || $matches[2]) { $colclass = $matches[1]; $colid = $matches[2]; if ($colclass) { $clsty = preg_replace('/\([A-Za-z0-9_\- ]+?(#.*?)?\)/', '', $clsty); } elseif ($colid) { $clsty = preg_replace('/\(#.+?\)/', '', $clsty); } } } if (preg_match('/(\(+)/', $clsty, $matches)) { $colpadl = strlen($matches[1]); $clsty = preg_replace('/\(+/', '', $clsty, 1); } if (preg_match('/(\)+)/', $clsty, $matches)) { $colpadr = strlen($matches[1]); $clsty = preg_replace('/\)+/', '', $clsty, 1); } if (preg_match('/\[(.+?)\]/', $clsty, $matches)) { $collang = $matches[1]; $clsty = preg_replace('/\[.+?\]/', '', $clsty, 1); } if ($clsty) { continue; } } if (preg_match('/^\\\\(\d+)/', $colparams, $matches)) { $colspan = $matches[1]; $colparams = substr($colparams, strlen($matches[1]) + 1); if ($matches[1]) { continue; } } if (preg_match('/\/(\d+)/', $colparams, $matches)) { if ($matches[1]) { $rowspans[$c] = $matches[1]; } $colparams = substr($colparams, strlen($matches[1]) + 1); if ($matches[1]) { continue; } } $params = 0; } } if (strlen($attrs)) { if (preg_match('/_/', $attrs)) { $header = 1; } if (preg_match('{(' . $this->alignre . ')}', $attrs, $matches) && strlen($matches[1])) { $colalign = ''; } // determine column alignment if (preg_match('/<>/', $attrs)) { $colalign .= '<>'; } elseif (preg_match('//', $attrs)) { $colalign = '>'; } if (preg_match('/\^/', $attrs)) { $colalign .= '^'; } elseif (preg_match('/~/', $attrs)) { $colalign .= '~'; } elseif (preg_match('/-/', $attrs)) { $colalign .= '-'; } } if ($rowheader) { $header = 1; } if ($header) { $colaligns[$c] = $colalign; } $col = preg_replace('/^ +/', '', $col, 1); $col = preg_replace('/ +$/', '', $col, 1); if (strlen($col)) { // create one cell tag $rowspan = $rowspans[$c] || 0; $col_out = '<' . ($header ? 'th' : 'td'); if ($colalign) { // horizontal, vertical alignment $halign = $this->_halign($colalign); if ($halign) { $col_out .= " align=\"$halign\""; } $valign = $this->_valign($colalign); if ($valign) { $col_out .= " valign=\"$valign\""; } } // apply css attributes, row, column spans if ($colpadl) { $colstyle .= ";padding-left:${colpadl}em"; } if ($colpadr) { $colstyle .= ";padding-right:${colpadr}em"; } if ($colclass) { $col_out .= " class=\"$colclass\""; } if ($colid) { $col_out .= " id=\"$colid\""; } if ($colstyle) { $colstyle = preg_replace('/^;/', '', $colstyle, 1); } if ($colstyle) { $col_out .= " style=\"$colstyle\""; } if ($collang) { $col_out .= " lang=\"$collang\""; } if ($colspan > 1) { $col_out .= " colspan=\"$colspan\""; } if (($rospan || 0) > 1) { $col_out .= " rowspan=\"$rowspan\""; } $col_out .= '>'; // if the content of this cell has newlines OR matches // our paragraph block signature, process it as a full-blown // textile document if (preg_match('/\n\n/', $col) || preg_match('{^(?:' . $this->halignre . '|' . $this->clstypadre . '*)* [\*\#] (?:' . $this->clstypadre . '*|' . $this->halignre . ')*\ }x', $col)) { $col_out .= $this->process($col); } else { $col_out .= $this->format_paragraph(array('text' => $col)); } $col_out .= ''; $row_out = $col_out . $row_out; if ($colspan) { $colspan = 0; } } else { if ($colspan == 0) { $colspan = 1; } $colspan++; } } if ($colspan > 1) { // handle the spanned column if we came up short $colspan--; $row_out = " 1 ? " colspan=\"$colspan\"" : '') . ">$row_out"; } // build one table row $out .= "_valign($rowalign); if ($valign) { $out .= " valign=\"$valign\""; } } if ($rowclass) { $out .= " class=\"$rowclass\""; } if ($rowid) { $out .= " id=\"$rowid\""; } if ($rowstyle) { $out .= " style=\"$rowstyle\""; } $out .= ">$row_out"; } // now, form the table tag itself $table = ''; $table .= "options['css_mode']) { // horizontal alignment $alignment = $this->_halign($talign); if ($talign == '=') { $tstyle .= ';margin-left:auto;margin-right:auto'; } else { if ($alignment) { $tstyle .= ';float:' . $alignment; } } if ($alignment) { $tclass .= ' ' . $alignment; } } else { $alignment = $this->_halign($talign); if ($alignment) { $table .= " align=\"$alignment\""; } } } if ($tpadl) { $tstyle .= ";padding-left:${tpadl}em"; } if ($tpadr) { $tstyle .= ";padding-right:${tpadr}em"; } if ($tclass) { $tclass = preg_replace('/^ /', '', $tclass, 1); } if ($tclass) { $table .= " class=\"$tclass\""; } if ($tid) { $table .= " id=\"$tid\""; } if ($tstyle) { $tstyle = preg_replace('/^;/', '', $tstyle, 1); } if ($tstyle) { $table .= " style=\"$tstyle\""; } if ($tlang) { $table .= " lang=\"$tlang\""; } if ($tclass || $tid || $tstyle) { $table .= " cellspacing=\"0\""; } $table .= ">$out"; if (preg_match('||', $table)) { // exception -- something isn't right so return fail case return NULL; } return $table; } // function format_table /** * The following attributes are allowed: * *
    * *
  • text * * The text to be processed.
  • * *
  • filters * * An array reference of filter names to run for the given text.
  • * *
* * @param $args An @c array specifying the text and filters to * apply. * * @return A @c string containing the filtered text. * * @private */ function apply_filters($args) { $text = $args['text']; if (!$text) { return ''; } $list = $args['filters']; $filters = $this->options['filters']; if (!is_array($filters)) { return $text; } $param = $this->filter_param(); foreach ($list as $filter) { if (!$filters[$filter]) { continue; } if (is_string($filters[$filter])) { $text = (($f = create_function('$text, $param', $filters[$filter])) ? $f($text, $param) : $text); } } return $text; } // function apply_filters // minor utility / formatting routines var $Have_Entities = 1; /** * Encodes input @c $html string, escaping characters as needed * to HTML entities. This relies on the @c htmlentities function * for full effect. If unavailable, @c encode_html_basic is used * as a fallback technique. If the "char_encoding" flag is * set to false, @c encode_html_basic is used exclusively. * * @param $html A @c string specifying the HTML to be encoded. * @param $can_double_encode If provided, a @c bool indicating * whether or not ampersand characters should be * unconditionally encoded. * * @return A @c string containing the encoded HTML. * * @private */ function encode_html($html, $can_double_encode = FALSE) { if (!$html) { return ''; } if ($this->Have_Entities && $this->options['char_encoding']) { $html = htmlentities($html); } else { $html = $this->encode_html_basic($html, $can_double_encode); } return $html; } // function encode_html /** * Decodes HTML entities in @c $html to their natural character * equivalents. * * @param $html A @c string specifying the HTML to be decoded. * * @return A @c string containing the decode HTML * * @private */ function decode_html($html) { $html = preg_replace('!"!', '"', $html); $html = preg_replace('!&!', '&', $html); $html = preg_replace('!<!', '<', $html); $html = preg_replace('!>!', '>', $html); return $html; } // function decode_html /** * Encodes the input @c $html string for the following characters: * \<, \>, & and ". If @c $can_double_encode is true, all * ampersand characters are escaped even if they already were. * If @c $can_double_encode is false, ampersands are only escaped * when they aren't part of a HTML entity already. * * @param $html A @c string specifying the HTML to be encoded. * @param $can_double_encode If provided, a @c bool indicating * whether or not ampersand characters should be * unconditionally encoded. * * @return A @c string containing the encoded HTML. * * @private */ function encode_html_basic($html, $can_double_encode = FALSE) { if (!$html) { return ''; } if (!preg_match('/[^\w\s]/', $html)) { return $html; } if ($can_double_encode) { $html = preg_replace('!&!', '&', $html); } else { // Encode any & not followed by something that looks like // an entity, numeric or otherwise. $html = preg_replace('/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w{1,8});)/', '&', $html); } $html = preg_replace('!"!', '"', $html); $html = preg_replace('!!', '>', $html); return $html; } // function encode_html_basic /** * Returns the size for the image identified in @c $file. This * method relies upon the @c getimagesize function. If unavailable, * @c image_size will return @c NULL. Otherwise, the expected return * value is an array of the width and height (in that order), in * pixels. * * @param $file A @c string specifying the path or URL for the image * file. * * @return An @c array containing the width and height * (respectively) of the image. * * @private */ function image_size($file) { $Have_ImageSize = function_exists('getimagesize'); if ($Have_ImageSize) { if (file_exists($file)) { return @getimagesize($file); } else { if ($docroot = ($this->docroot() ? $this->docroot() : $_SERVER['DOCUMENT_ROOT'])) { $fullpath = $docroot . preg_replace('|^/*(.*)$|', '/$1', $file); if (file_exists($fullpath)) { return @getimagesize($fullpath); } } } } return @getimagesize($file); } // function image_size /** * Encodes the query portion of a URL, escaping characters * as necessary. * * @param $str A @c string specifying the URL to be encoded. * * @return A @c string containing the encoded URL. * * @private */ function encode_url($str) { $str = preg_replace_callback('!([^A-Za-z0-9_\.\-\+\&=%;])!x', $this->_cb('ord($m[1]) > 255 ? \'%u\' . sprintf("%04X", ord($m[1])) : \'%\' . sprintf("%02X", ord($m[1]))'), $str); return $str; } // function encode_url /** * Encodes the email address in @c $addr for 'mailto:' links. * * @param $addr A @c string specifying the email address to encode. * * @return A @c string containing the encoded email address. * * @private */ function mail_encode($addr) { // granted, this is simple, but it gives off warm fuzzies $addr = preg_replace_callback('!([^\$])!x', $this->_cb('ord($m[1]) > 255 ? \'%u\' . sprintf("%04X", ord($m[1])) : \'%\' . sprintf("%02X", ord($m[1]))'), $addr); return $addr; } // function mail_encode /** * Processes string, formatting plain quotes into curly quotes. * * @param $str A @c string specifying the text to process. * * @return A @c string containing the processed text. * * @private */ function process_quotes($str) { // stub routine for now. subclass and implement. return $str; } // function process_quotes // a default set of macros for the {...} macro syntax // just a handy way to write a lot of the international characters // and some commonly used symbols /** * Returns an associative @c array of macros that are assigned to be processed by * default within the @c format_inline method. * * @return An @c array containing the default macros. * * @private */ function default_macros() { // <, >, " must be html entities in the macro text since // those values are escaped by the time they are processed // for macros. return array( 'c|' => '¢', // CENT SIGN '|c' => '¢', // CENT SIGN 'L-' => '£', // POUND SIGN '-L' => '£', // POUND SIGN 'Y=' => '¥', // YEN SIGN '=Y' => '¥', // YEN SIGN '(c)' => '©', // COPYRIGHT SIGN '<<' => '«', // LEFT-POINTING DOUBLE ANGLE QUOTATION '(r)' => '®', // REGISTERED SIGN '+_' => '±', // PLUS-MINUS SIGN '_+' => '±', // PLUS-MINUS SIGN '>>' => '»', // RIGHT-POINTING DOUBLE ANGLE QUOTATION '1/4' => '¼', // VULGAR FRACTION ONE QUARTER '1/2' => '½', // VULGAR FRACTION ONE HALF '3/4' => '¾', // VULGAR FRACTION THREE QUARTERS 'A`' => 'À', // LATIN CAPITAL LETTER A WITH GRAVE '`A' => 'À', // LATIN CAPITAL LETTER A WITH GRAVE 'A\'' => 'Á', // LATIN CAPITAL LETTER A WITH ACUTE '\'A' => 'Á', // LATIN CAPITAL LETTER A WITH ACUTE 'A^' => 'Â', // LATIN CAPITAL LETTER A WITH CIRCUMFLEX '^A' => 'Â', // LATIN CAPITAL LETTER A WITH CIRCUMFLEX 'A~' => 'Ã', // LATIN CAPITAL LETTER A WITH TILDE '~A' => 'Ã', // LATIN CAPITAL LETTER A WITH TILDE 'A"' => 'Ä', // LATIN CAPITAL LETTER A WITH DIAERESIS '"A' => 'Ä', // LATIN CAPITAL LETTER A WITH DIAERESIS 'Ao' => 'Å', // LATIN CAPITAL LETTER A WITH RING ABOVE 'oA' => 'Å', // LATIN CAPITAL LETTER A WITH RING ABOVE 'AE' => 'Æ', // LATIN CAPITAL LETTER AE 'C,' => 'Ç', // LATIN CAPITAL LETTER C WITH CEDILLA ',C' => 'Ç', // LATIN CAPITAL LETTER C WITH CEDILLA 'E`' => 'È', // LATIN CAPITAL LETTER E WITH GRAVE '`E' => 'È', // LATIN CAPITAL LETTER E WITH GRAVE 'E\'' => 'É', // LATIN CAPITAL LETTER E WITH ACUTE '\'E' => 'É', // LATIN CAPITAL LETTER E WITH ACUTE 'E^' => 'Ê', // LATIN CAPITAL LETTER E WITH CIRCUMFLEX '^E' => 'Ê', // LATIN CAPITAL LETTER E WITH CIRCUMFLEX 'E"' => 'Ë', // LATIN CAPITAL LETTER E WITH DIAERESIS '"E' => 'Ë', // LATIN CAPITAL LETTER E WITH DIAERESIS 'I`' => 'Ì', // LATIN CAPITAL LETTER I WITH GRAVE '`I' => 'Ì', // LATIN CAPITAL LETTER I WITH GRAVE 'I\'' => 'Í', // LATIN CAPITAL LETTER I WITH ACUTE '\'I' => 'Í', // LATIN CAPITAL LETTER I WITH ACUTE 'I^' => 'Î', // LATIN CAPITAL LETTER I WITH CIRCUMFLEX '^I' => 'Î', // LATIN CAPITAL LETTER I WITH CIRCUMFLEX 'I"' => 'Ï', // LATIN CAPITAL LETTER I WITH DIAERESIS '"I' => 'Ï', // LATIN CAPITAL LETTER I WITH DIAERESIS 'D-' => 'Ð', // LATIN CAPITAL LETTER ETH '-D' => 'Ð', // LATIN CAPITAL LETTER ETH 'N~' => 'Ñ', // LATIN CAPITAL LETTER N WITH TILDE '~N' => 'Ñ', // LATIN CAPITAL LETTER N WITH TILDE 'O`' => 'Ò', // LATIN CAPITAL LETTER O WITH GRAVE '`O' => 'Ò', // LATIN CAPITAL LETTER O WITH GRAVE 'O\'' => 'Ó', // LATIN CAPITAL LETTER O WITH ACUTE '\'O' => 'Ó', // LATIN CAPITAL LETTER O WITH ACUTE 'O^' => 'Ô', // LATIN CAPITAL LETTER O WITH CIRCUMFLEX '^O' => 'Ô', // LATIN CAPITAL LETTER O WITH CIRCUMFLEX 'O~' => 'Õ', // LATIN CAPITAL LETTER O WITH TILDE '~O' => 'Õ', // LATIN CAPITAL LETTER O WITH TILDE 'O"' => 'Ö', // LATIN CAPITAL LETTER O WITH DIAERESIS '"O' => 'Ö', // LATIN CAPITAL LETTER O WITH DIAERESIS 'O/' => 'Ø', // LATIN CAPITAL LETTER O WITH STROKE '/O' => 'Ø', // LATIN CAPITAL LETTER O WITH STROKE 'U`' => 'Ù', // LATIN CAPITAL LETTER U WITH GRAVE '`U' => 'Ù', // LATIN CAPITAL LETTER U WITH GRAVE 'U\'' => 'Ú', // LATIN CAPITAL LETTER U WITH ACUTE '\'U' => 'Ú', // LATIN CAPITAL LETTER U WITH ACUTE 'U^' => 'Û', // LATIN CAPITAL LETTER U WITH CIRCUMFLEX '^U' => 'Û', // LATIN CAPITAL LETTER U WITH CIRCUMFLEX 'U"' => 'Ü', // LATIN CAPITAL LETTER U WITH DIAERESIS '"U' => 'Ü', // LATIN CAPITAL LETTER U WITH DIAERESIS 'Y\'' => 'Ý', // LATIN CAPITAL LETTER Y WITH ACUTE '\'Y' => 'Ý', // LATIN CAPITAL LETTER Y WITH ACUTE 'a`' => 'à', // LATIN SMALL LETTER A WITH GRAVE '`a' => 'à', // LATIN SMALL LETTER A WITH GRAVE 'a\'' => 'á', // LATIN SMALL LETTER A WITH ACUTE '\'a' => 'á', // LATIN SMALL LETTER A WITH ACUTE 'a^' => 'â', // LATIN SMALL LETTER A WITH CIRCUMFLEX '^a' => 'â', // LATIN SMALL LETTER A WITH CIRCUMFLEX 'a~' => 'ã', // LATIN SMALL LETTER A WITH TILDE '~a' => 'ã', // LATIN SMALL LETTER A WITH TILDE 'a"' => 'ä', // LATIN SMALL LETTER A WITH DIAERESIS '"a' => 'ä', // LATIN SMALL LETTER A WITH DIAERESIS 'ao' => 'å', // LATIN SMALL LETTER A WITH RING ABOVE 'oa' => 'å', // LATIN SMALL LETTER A WITH RING ABOVE 'ae' => 'æ', // LATIN SMALL LETTER AE 'c,' => 'ç', // LATIN SMALL LETTER C WITH CEDILLA ',c' => 'ç', // LATIN SMALL LETTER C WITH CEDILLA 'e`' => 'è', // LATIN SMALL LETTER E WITH GRAVE '`e' => 'è', // LATIN SMALL LETTER E WITH GRAVE 'e\'' => 'é', // LATIN SMALL LETTER E WITH ACUTE '\'e' => 'é', // LATIN SMALL LETTER E WITH ACUTE 'e^' => 'ê', // LATIN SMALL LETTER E WITH CIRCUMFLEX '^e' => 'ê', // LATIN SMALL LETTER E WITH CIRCUMFLEX 'e"' => 'ë', // LATIN SMALL LETTER E WITH DIAERESIS '"e' => 'ë', // LATIN SMALL LETTER E WITH DIAERESIS 'i`' => 'ì', // LATIN SMALL LETTER I WITH GRAVE '`i' => 'ì', // LATIN SMALL LETTER I WITH GRAVE 'i\'' => 'í', // LATIN SMALL LETTER I WITH ACUTE '\'i' => 'í', // LATIN SMALL LETTER I WITH ACUTE 'i^' => 'î', // LATIN SMALL LETTER I WITH CIRCUMFLEX '^i' => 'î', // LATIN SMALL LETTER I WITH CIRCUMFLEX 'i"' => 'ï', // LATIN SMALL LETTER I WITH DIAERESIS '"i' => 'ï', // LATIN SMALL LETTER I WITH DIAERESIS 'n~' => 'ñ', // LATIN SMALL LETTER N WITH TILDE '~n' => 'ñ', // LATIN SMALL LETTER N WITH TILDE 'o`' => 'ò', // LATIN SMALL LETTER O WITH GRAVE '`o' => 'ò', // LATIN SMALL LETTER O WITH GRAVE 'o\'' => 'ó', // LATIN SMALL LETTER O WITH ACUTE '\'o' => 'ó', // LATIN SMALL LETTER O WITH ACUTE 'o^' => 'ô', // LATIN SMALL LETTER O WITH CIRCUMFLEX '^o' => 'ô', // LATIN SMALL LETTER O WITH CIRCUMFLEX 'o~' => 'õ', // LATIN SMALL LETTER O WITH TILDE '~o' => 'õ', // LATIN SMALL LETTER O WITH TILDE 'o"' => 'ö', // LATIN SMALL LETTER O WITH DIAERESIS '"o' => 'ö', // LATIN SMALL LETTER O WITH DIAERESIS ':-' => '÷', // DIVISION SIGN '-:' => '÷', // DIVISION SIGN 'o/' => 'ø', // LATIN SMALL LETTER O WITH STROKE '/o' => 'ø', // LATIN SMALL LETTER O WITH STROKE 'u`' => 'ù', // LATIN SMALL LETTER U WITH GRAVE '`u' => 'ù', // LATIN SMALL LETTER U WITH GRAVE 'u\'' => 'ú', // LATIN SMALL LETTER U WITH ACUTE '\'u' => 'ú', // LATIN SMALL LETTER U WITH ACUTE 'u^' => 'û', // LATIN SMALL LETTER U WITH CIRCUMFLEX '^u' => 'û', // LATIN SMALL LETTER U WITH CIRCUMFLEX 'u"' => 'ü', // LATIN SMALL LETTER U WITH DIAERESIS '"u' => 'ü', // LATIN SMALL LETTER U WITH DIAERESIS 'y\'' => 'ý', // LATIN SMALL LETTER Y WITH ACUTE '\'y' => 'ý', // LATIN SMALL LETTER Y WITH ACUTE 'y"' => 'ÿ', // LATIN SMALL LETTER Y WITH DIAERESIS '"y' => 'ÿ', // LATIN SMALL LETTER Y WITH DIAERESIS 'OE' => 'Œ', // LATIN CAPITAL LIGATURE OE 'oe' => 'œ', // LATIN SMALL LIGATURE OE '*' => '•', // BULLET 'Fr' => '₣', // FRENCH FRANC SIGN 'L=' => '₤', // LIRA SIGN '=L' => '₤', // LIRA SIGN 'Rs' => '₨', // RUPEE SIGN 'C=' => '€', // EURO SIGN '=C' => '€', // EURO SIGN 'tm' => '™', // TRADE MARK SIGN '<-' => '←', // LEFTWARDS ARROW '->' => '→', // RIGHTWARDS ARROW '<=' => '⇐', // LEFTWARDS DOUBLE ARROW '=>' => '⇒', // RIGHTWARDS DOUBLE ARROW '=/' => '≠', // NOT EQUAL TO '/=' => '≠', // NOT EQUAL TO '<_' => '≤', // LESS-THAN OR EQUAL TO '_<' => '≤', // LESS-THAN OR EQUAL TO '>_' => '≥', // GREATER-THAN OR EQUAL TO '_>' => '≥', // GREATER-THAN OR EQUAL TO ':(' => '☹', // WHITE FROWNING FACE ':)' => '☺', // WHITE SMILING FACE 'spade' => '♠', // BLACK SPADE SUIT 'club' => '♣', // BLACK CLUB SUIT 'heart' => '♥', // BLACK HEART SUIT 'diamond' => '♦', // BLACK DIAMOND SUIT ); } // function default_macros // "private", internal routines /** * Sets the default CSS names for CSS controlled markup. This * is an internal function that should not be called directly. * * @private */ function _css_defaults() { $css_defaults = array( 'class_align_right' => 'right', 'class_align_left' => 'left', 'class_align_center' => 'center', 'class_align_top' => 'top', 'class_align_bottom' => 'bottom', 'class_align_middle' => 'middle', 'class_align_justify' => 'justify', 'class_caps' => 'caps', 'class_footnote' => 'footnote', 'id_footnote_prefix' => 'fn', ); $this->css($css_defaults); } // function _css_defaults /** * Returns the alignment keyword depending on the symbol passed. * *
    * *
  • \<\> * * becomes 'justify'
  • * *
  • \< * * becomes 'left'
  • * *
  • \> * * becomes 'right'
  • * *
  • = * * becomes 'center'
  • * *
* * @param $align A @c string specifying the alignment code. * * @return A @c string containing the alignment text. * * @private */ function _halign($align) { if (preg_match('/<>/', $align)) { return 'justify'; } elseif (preg_match('//', $align)) { return 'right'; } elseif (preg_match('/=/', $align)) { return 'center'; } return ''; } // function _halign /** * Returns the alignment keyword depending on the symbol passed. * *
    * *
  • ^ * * becomes 'top'
  • * *
  • ~ * * becomes 'bottom'
  • * *
  • - * * becomes 'middle'
  • * *
* * @param $align A @c string specifying the alignment code. * * @return A @c string containing the alignment text. * * @private */ function _valign($align) { if (preg_match('/\^/', $align)) { return 'top'; } elseif (preg_match('/~/', $align)) { return 'bottom'; } elseif (preg_match('/-/', $align)) { return 'middle'; } return ''; } // function _valign /** * Returns the alignment keyword depending on the symbol passed. * The following alignment symbols are recognized, and given * preference in the order listed: * *
    * *
  • ^ * * becomes 'top'
  • * *
  • ~ * * becomes 'bottom'
  • * *
  • - * * becomes 'middle'
  • * *
  • \< * * becomes 'left'
  • * *
  • \> * * becomes 'right'
  • * *
* * @param $align A @c string containing the alignment code. * * @return A @c string containing the alignment text. * * @private */ function _imgalign($align) { $align = preg_replace('/(<>|=)/', '', $align); return ($this->_valign($align) ? $this->_valign($align) : $this->_halign($align)); } // function _imgalign /** * This utility routine will take 'border' characters off of * the given @c $pre and @c $post strings if they match one of these * conditions: *
   *     $pre starts with '[', $post ends with ']'
   *     $pre starts with '{', $post ends with '}'
   * 
* If neither condition is met, then the @c $pre and @c $post * values are left untouched. * * @param $pre A @c string specifying the prefix. * @param $post A @c string specifying the postfix. * * @private */ function _strip_borders(&$pre, &$post) { if ($post && $pre && preg_match('/[{[]/', ($open = substr($pre, 0, 1)))) { $close = substr($post, 0, 1); if ((($open == '{') && ($close == '}')) || (($open == '[') && ($close == ']'))) { $pre = substr($pre, 1); $post = substr($post, 1); } else { if (!preg_match('/[}\]]/', $close)) { $close = substr($post, -1, 1); } if ((($open == '{') && ($close == '}')) || (($open == '[') && ($close == ']'))) { $pre = substr($pre, 1); $post = substr($post, 0, strlen($post) - 1); } } } } // function _strip_borders /** * An internal routine that takes a string and appends it to an array. * It returns a marker that is used later to restore the preserved * string. * * @param $array The @c array in which to store the replacement * text. * @param $str A @c string specifying the replacement text. * * @return A @c string containing a temporary marker for the * replacement. * * @private */ function _repl(&$array, $str) { $array[] = $str; return ''; } // function _repl /** * An internal routine responsible for breaking up a string into * individual tag and plaintext elements. * * @param $str A @c string specifying the text to tokenize. * * @return An @c array containing the tag and text tokens. * * @private */ function _tokenize($str) { $pos = 0; $len = strlen($str); unset($tokens); $depth = 6; $nested_tags = substr(str_repeat('(?:]|', $depth), 0, -1) . str_repeat(')*>)', $depth); $match = '(?s: )| # comment (?s: <\? .*? \?> )| # processing instruction (?s: <% .*? %> )| # ASP-like (?:' . $nested_tags . ')| (?:' . $this->codere . ')'; // nested tags while (preg_match('{(' . $match . ')}x', substr($str, $pos), $matches, PREG_OFFSET_CAPTURE)) { $whole_tag = $matches[1][0]; $sec_start = $pos + $matches[1][1] + strlen($whole_tag); $tag_start = $sec_start - strlen($whole_tag); if ($pos < $tag_start) { $tokens[] = array('text', substr($str, $pos, $tag_start - $pos)); } if (preg_match('/^[[{]?@/', $whole_tag)) { $tokens[] = array('text', $whole_tag); } else { // this clever hack allows us to preserve \n within tags. // this is restored at the end of the format_paragraph method //$whole_tag = preg_replace('/\n/', "\r", $whole_tag); $whole_tag = preg_replace('/\n/', "\001", $whole_tag); $tokens[] = array('tag', $whole_tag); } $pos = $sec_start; } if ($pos < $len) { $tokens[] = array('text', substr($str, $pos, $len - $pos)); } return $tokens; } // function _tokenize /** * Returns the version of this release of Textile.php. *JHR* * * @return An @c array with keys 'text' and 'build' containing the * text version and build ID of this release, respectively. * * @static */ function version() { /* Why text and an ID? Well, the text is easier for the user to * read and understand while the build ID, being a number (a date * with a serial, specifically), is easier for the developer to * use to determine newer/older versions for upgrade and * installation purposes. */ return array("text" => "2.0.3", "build" => 2004060300); } // function version /** * Creates a custom callback function from the provided PHP * code. The result is used as the callback in * @c preg_replace_callback calls. *JHR* * * @param $function A @c string specifying the PHP code for the * function body. * * @return A @c function to be used for the callback. * * @private */ function _cb($function) { $current =& Textile::_current_store($this); return create_function('$m', '$this =& Textile::_current(); return ' . $function . ';'); } // function _cb /** * Stores a static variable for the Textile class. This helper * function is used by @c _current to simulate a static * class variable in PHP. *JHR* * * @param $new If a non-@c NULL object reference, the Textile object * to be set as the current object. * * @return The @c array containing a reference to the current * Textile object at index 0. An array is used because PHP * does not allow static variables to be references. * * @static * @private */ /* static */ function &_current_store(&$new) { static $current = array(); if ($new != NULL) { $current = array(&$new); } return $current; } // function _current_store /** * Returns the "current" Textile object. This is used within * anonymous callback functions which cannot have the scope of a * specific object. *JHR* * * @return An @c object reference to the current Textile object. * * @static * @private */ /* static */ function &_current() { $current =& Textile::_current_store($null = NULL); return $current[0]; } // function _current } // class Textile /** * Brad Choate's mttextile Movable Type plugin adds some additional * functionality to the Textile.pm Perl module. This includes optional * "SmartyPants" processing of text to produce smart quotes, dashes, * etc., code colorizing using Beautifier, and some special lookup * links (imdb, google, dict, and amazon). The @c MTLikeTextile class * is a subclass of @c Textile that provides an MT-like implementation * of Textile to produce results similar to that of the mttextile * plugin. Currently only the SmartyPants and special lookup links are * implemented. * * Using the @c MTLikeTextile class is exactly the same as using @c * Textile. Simply use $textile = new MTLikeTextile; * instead of $textile = new Textile; to create a Textile * object. This will enable the special lookup links. To enable * SmartyPants processing, you must install the SmartyPants-PHP * implementation available at * http://monauraljerk.org/smartypants-php/ * and include the * SmartyPants-PHP.inc file. * *

 * include_once("Textile.php");
 * include_once("SmartyPants-PHP.inc");
 * $text = \<\<\process($text);
 * print $html;
 * 
* * @brief A Textile implementation providing additional * Movable-Type-like formatting to produce results similar to * the mttextile plugin. * * @author Jim Riggs \ */ class MTLikeTextile extends Textile { /** * Instantiates a new MTLikeTextile object. Optional options * can be passed to initialize the object. Attributes for the * options key are the same as the get/set method names * documented here. * * @param $options The @c array specifying the options to use for * this object. * * @public */ function MTLikeTextile($options = array()) { parent::Textile($options); } // function MTLikeTextile /** * @private */ function process_quotes($str) { if (!$this->options['do_quotes'] || !function_exists('SmartyPants')) { return $str; } return SmartyPants($str, $this->options['smarty_mode']); } // function process_quotes /** * @private */ function format_url($args) { $url = ($args['url'] ? $args['url'] : ''); if (preg_match('/^(imdb|google|dict|amazon)(:(.+))?$/x', $url, $matches)) { $term = $matches[3]; $term = ($term ? $term : strip_tags($args['linktext'])); switch ($matches[1]) { case 'imdb': $args['url'] = 'http://www.imdb.com/Find?for=' . $term; break; case 'google': $args['url'] = 'http://www.google.com/search?q=' . $term; break; case 'dict': $args['url'] = 'http://www.dictionary.com/search?q=' . $term; break; case 'amazon': $args['url'] = 'http://www.amazon.com/exec/obidos/external-search?index=blended&keyword=' . $term; break; } } return parent::format_url($args); } // function format_url } // class MTLikeTextile /** * @mainpage * Textile - A Humane Web Text Generator. * * @section synopsis SYNOPSIS * *

 * include_once("Textile.php");
 * $text = \<\<\process($text);
 * print $html;
 * 
* * @section abstract ABSTRACT * * Textile.php is a PHP-based implementation of Dean Allen's Textile * syntax. Textile is shorthand for doing common formatting tasks. * * @section syntax SYNTAX * * Textile processes text in units of blocks and lines. * A block might also be considered a paragraph, since blocks * are separated from one another by a blank line. Blocks * can begin with a signature that helps identify the rest * of the block content. Block signatures include: * *
    * *
  • p * * A paragraph block. This is the default signature if no * signature is explicitly given. Paragraphs are formatted * with all the inline rules (see inline formatting) and * each line receives the appropriate markup rules for * the flavor of HTML in use. For example, newlines for XHTML * content receive a \
    tag at the end of the line * (with the exception of the last line in the paragraph). * Paragraph blocks are enclosed in a \ tag.
  • * *
  • pre * * A pre-formatted block of text. Textile will not add any * HTML tags for individual lines. Whitespace is also preserved. * * Note that within a "pre" block, \< and \> are * translated into HTML entities automatically.
  • * *
  • bc * * A "bc" signature is short for "block code", which implies * a preformatted section like the 'pre' block, but it also * gets a \ tag (or for XHTML 2, a \ * tag is used instead). * * Note that within a "bc" block, \< and \> are * translated into HTML entities automatically.
  • * *
  • table * * For composing HTML tables. See the "TABLES" section for more * information.
  • * *
  • bq * * A "bq" signature is short for "block quote". Paragraph text * formatting is applied to these blocks and they are enclosed * in a \ tag as well as \ tags * within.
  • * *
  • h1, h2, h3, h4, h5, h6 * * Headline signatures that produce \, etc. tags. * You can adjust the relative output of these using the * head_offset attribute.
  • * *
  • clear * * A 'clear' signature is simply used to indicate that the next * block should emit a CSS style attribute that clears any * floating elements. The default behavior is to clear "both", * but you can use the left (\< or right \>) alignment * characters to indicate which side to clear.
  • * *
  • dl * * A "dl" signature is short for "definition list". See the * "LISTS" section for more information.
  • * *
  • fn * * A "fn" signature is short for "footnote". You add a number * following the "fn" keyword to number the footnote. Footnotes * are output as paragraph tags but are given a special CSS * class name which can be used to style them as you see fit.
  • * *
* * All signatures should end with a period and be followed * with a space. Inbetween the signature and the period, you * may use several parameters to further customize the block. * These include: * *
    * *
  • {style rule} * * A CSS style rule. Style rules can span multiple lines.
  • * *
  • [ll] * * A language identifier (for a "lang" attribute).
  • * *
  • (class) or (#id) or (class#id) * * For CSS class and id attributes.
  • * *
  • \>, \<, =, \<\> * * Modifier characters for alignment. Right-justification, left-justification, * centered, and full-justification.
  • * *
  • ( (one or more) * * Adds padding on the left. 1em per "(" character is applied. * When combined with the align-left or align-right modifier, * it makes the block float.
  • * *
  • ) (one or more) * * Adds padding on the right. 1em per ")" character is applied. * When combined with the align-left or align-right modifier, * it makes the block float.
  • * *
  • |filter| or |filter|filter|filter| * * A filter may be invoked to further format the text for this * signature. If one or more filters are identified, the text * will be processed first using the filters and then by * Textile's own block formatting rules.
  • * *
* * @subsection extendedblocks Extended Blocks * * Normally, a block ends with the first blank line encountered. * However, there are situations where you may want a block to continue * for multiple paragraphs of text. To cause a given block signature * to stay active, use two periods in your signature instead of one. * This will tell Textile to keep processing using that signature * until it hits the next signature is found. * * For example: *
 *     bq.. This is paragraph one of a block quote.
 *
 *     This is paragraph two of a block quote.
 *
 *     p. Now we're back to a regular paragraph.
 * 
* You can apply this technique to any signature (although for * some it doesn't make sense, like "h1" for example). This is * especially useful for "bc" blocks where your code may * have many blank lines scattered through it. * * @subsection escaping Escaping * * Sometimes you want Textile to just get out of the way and * let you put some regular HTML markup in your document. You * can disable Textile formatting for a given block using the '==' * escape mechanism: *
 *     p. Regular paragraph
 *
 *     ==
 *     Escaped portion -- will not be formatted
 *     by Textile at all
 *     ==
 *
 *     p. Back to normal.
 * 
* You can also use this technique within a Textile block, * temporarily disabling the inline formatting functions: *
 *     p. This is ==*a test*== of escaping.
 * 
* @subsection inlineformatting Inline Formatting * * Formatting within a block of text is covered by the "inline" * formatting rules. These operators must be placed up against * text/punctuation to be recognized. These include: * *
    * *
  • *strong* * * Translates into \strong\.
  • * *
  • _emphasis_ * * Translates into \emphasis\.
  • * *
  • **bold** * * Translates into \bold\.
  • * *
  • __italics__ * * Translates into \italics\.
  • * *
  • ++bigger++ * * Translates into \bigger\.
  • * *
  • --smaller-- * * Translates into: \smaller\.
  • * *
  • -deleted text- * * Translates into \deleted text\.
  • * *
  • +inserted text+ * * Translates into \inserted text\.
  • * *
  • ^superscript^ * * Translates into \superscript\.
  • * *
  • ~subscript~ * * Translates into \subscript\.
  • * *
  • \%span\% * * Translates into \span\.
  • * *
  • \@code\@ * * Translates into \code\. Note * that within a '\@...\@' section, \< and \> are * translated into HTML entities automatically.
  • * *
* * Inline formatting operators accept the following modifiers: * *
    * *
  • {style rule} * * A CSS style rule.
  • * *
  • [ll] * * A language identifier (for a "lang" attribute).
  • * *
  • (class) or (#id) or (class#id) * * For CSS class and id attributes.
  • * *
* * @subsubsection examples Examples *
 *     Textile is *way* cool.
 *
 *     Textile is *_way_* cool.
 * 
* Now this won't work, because the formatting * characters need whitespace before and after * to be properly recognized. *
 *     Textile is way c*oo*l.
 * 
* However, you can supply braces or brackets to * further clarify that you want to format, so * this would work: *
 *     Textile is way c[*oo*]l.
 * 
* @subsection footnotes Footnotes * * You can create footnotes like this: *
 *     And then he went on a long trip[1].
 * 
* By specifying the brackets with a number inside, Textile will * recognize that as a footnote marker. It will replace that with * a construct like this: *
 *     And then he went on a long
 *     trip1
 * 
* To supply the content of the footnote, place it at the end of your * document using a "fn" block signature: *
 *     fn1. And there was much rejoicing.
 * 
* Which creates a paragraph that looks like this: *
 *     

1 And there was * much rejoicing.

*
* @subsection links Links * * Textile defines a shorthand for formatting hyperlinks. * The format looks like this: *
 *     "Text to display":http://example.com
 * 
* In addition to this, you can add 'title' text to your link: *
 *     "Text to display (Title text)":http://example.com
 * 
* The URL portion of the link supports relative paths as well * as other protocols like ftp, mailto, news, telnet, etc. *
 *     "E-mail me please":mailto:someone\@example.com
 * 
* You can also use single quotes instead of double-quotes if * you prefer. As with the inline formatting rules, a hyperlink * must be surrounded by whitespace to be recognized (an * exception to this is common punctuation which can reside * at the end of the URL). If you have to place a URL next to * some other text, use the bracket or brace trick to do that: *
 *     You["gotta":http://example.com]seethis!
 * 
* Textile supports an alternate way to compose links. You can * optionally create a lookup list of links and refer to them * separately. To do this, place one or more links in a block * of it's own (it can be anywhere within your document): *
 *     [excom]http://example.com
 *     [exorg]http://example.org
 * 
* For a list like this, the text in the square brackets is * used to uniquely identify the link given. To refer to that * link, you would specify it like this: *
 *     "Text to display":excom
 * 
* Once you've defined your link lookup table, you can use * the identifiers any number of times. * * @subsection images Images * * Images are identified by the following pattern: *
 *     !/path/to/image!
 * 
* Image attributes may also be specified: *
 *     !/path/to/image 10x20!
 * 
* Which will render an image 10 pixels wide and 20 pixels high. * Another way to indicate width and height: *
 *     !/path/to/image 10w 20h!
 * 
* You may also redimension the image using a percentage. *
 *     !/path/to/image 20%x40%!
 * 
* Which will render the image at 20% of it's regular width * and 40% of it's regular height. * * Or specify one percentage to resize proprotionately: *
 *     !/path/to/image 20%!
 * 
* Alt text can be given as well: *
 *     !/path/to/image (Alt text)!
 * 
* The path of the image may refer to a locally hosted image or * can be a full URL. * * You can also use the following modifiers after the opening '!' * character: * *
    * *
  • \< * * Align the image to the left (causes the image to float if * CSS options are enabled).
  • * *
  • \> * * Align the image to the right (causes the image to float if * CSS options are enabled).
  • * *
  • - (dash) * * Aligns the image to the middle.
  • * *
  • ^ * * Aligns the image to the top.
  • * *
  • ~ (tilde) * * Aligns the image to the bottom.
  • * *
  • {style rule} * * Applies a CSS style rule to the image.
  • * *
  • (class) or (#id) or (class#id) * * Applies a CSS class and/or id to the image.
  • * *
  • ( (one or more) * * Pads 1em on the left for each '(' character.
  • * *
  • ) (one or more) * * Pads 1em on the right for each ')' character.
  • * *
* * @subsection characterreplacements Character Replacements * * A few simple, common symbols are automatically replaced: *
 *     (c)
 *     (r)
 *     (tm)
 * 
* In addition to these, there are a whole set of character * macros that are defined by default. All macros are enclosed * in curly braces. These include: *
 *     {c|} or {|c} cent sign
 *     {L-} or {-L} pound sign
 *     {Y=} or {=Y} yen sign
 * 
* Many of these macros can be guessed. For example: *
 *     {A'} or {'A}
 *     {a"} or {"a}
 *     {1/4}
 *     {*}
 *     {:)}
 *     {:(}
 * 
* @subsection lists Lists * * Textile also supports ordered and unordered lists. * You simply place an asterisk or pound sign, followed * with a space at the start of your lines. * * Simple lists: *
 *     * one
 *     * two
 *     * three
 * 
* Multi-level lists: *
 *     * one
 *     ** one A
 *     ** one B
 *     *** one B1
 *     * two
 *     ** two A
 *     ** two B
 *     * three
 * 
* Ordered lists: *
 *     # one
 *     # two
 *     # three
 * 
* Styling lists: *
 *     (class#id)* one
 *     * two
 *     * three
 * 
* The above sets the class and id attributes for the \ * tag. *
 *     *(class#id) one
 *     * two
 *     * three
 * 
* The above sets the class and id attributes for the first \ * tag. * * Definition lists: *
 *     dl. textile:a cloth, especially one manufactured by weaving
 *     or knitting; a fabric
 *     format:the arrangement of data for storage or display.
 * 
* Note that there is no space between the term and definition. The * term must be at the start of the line (or following the "dl" * signature as shown above). * * @subsection tables Tables * * Textile supports tables. Tables must be in their own block and * must have pipe characters delimiting the columns. An optional * block signature of "table" may be used, usually for applying * style, class, id or other options to the table element itself. * * From the simple: *
 *     |a|b|c|
 *     |1|2|3|
 * 
* To the complex: *
 *     table(fig). {color:red}_|Top|Row|
 *     {color:blue}|/2. Second|Row|
 *     |_{color:green}. Last|
 * 
* Modifiers can be specified for the table signature itself, * for a table row (prior to the first '|' character) and * for any cell (following the '|' for that cell). Note that for * cells, a period followed with a space must be placed after * any modifiers to distinguish the modifier from the cell content. * * Modifiers allowed are: * *
    * *
  • {style rule} * * A CSS style rule.
  • * *
  • (class) or (#id) or (class#id) * * A CSS class and/or id attribute.
  • * *
  • ( (one or more) * * Adds 1em of padding to the left for each '(' character.
  • * *
  • ) (one or more) * * Adds 1em of padding to the right for each ')' character.
  • * *
  • \< * * Aligns to the left (floats to left for tables if combined with the * ')' modifier).
  • * *
  • \> * * Aligns to the right (floats to right for tables if combined with * the '(' modifier).
  • * *
  • = * * Aligns to center (sets left, right margins to 'auto' for tables).
  • * *
  • \<\> * * For cells only. Justifies text.
  • * *
  • ^ * * For rows and cells only. Aligns to the top.
  • * *
  • ~ (tilde) * * For rows and cells only. Aligns to the bottom.
  • * *
  • _ (underscore) * * Can be applied to a table row or cell to indicate a header * row or cell.
  • * *
  • \\2 or \\3 or \\4, etc. * * Used within cells to indicate a colspan of 2, 3, 4, etc. columns. * When you see "\\", think "push forward".
  • * *
  • /2 or /3 or /4, etc. * * Used within cells to indicate a rowspan of 2, 3, 4, etc. rows. * When you see "/", think "push downward".
  • * *
* * When a cell is identified as a header cell and an alignment * is specified, that becomes the default alignment for * cells below it. You can always override this behavior by * specifying an alignment for one of the lower cells. * * @subsection cssnotes CSS Notes * * When CSS is enabled (and it is by default), CSS class names * are automatically applied in certain situations. * *
    * *
  • Aligning a block or span or other element to * left, right, etc. * * "left" for left justified, "right" for right justified, * "center" for centered text, "justify" for full-justified * text.
  • * *
  • Aligning an image to the top or bottom * * "top" for top alignment, "bottom" for bottom alignment, * "middle" for middle alignment.
  • * *
  • Footnotes * * "footnote" is applied to the paragraph tag for the * footnote text itself. An id of "fn" plus the footnote * number is placed on the paragraph for the footnote as * well. For the footnote superscript tag, a class of * "footnote" is used.
  • * *
  • Capped text * * For a series of characters that are uppercased, a * span is placed around them with a class of "caps".
  • * *
* * @subsection miscellaneous Miscellaneous * * Textile tries to do it's very best to ensure proper XHTML * syntax. It will even attempt to fix errors you may introduce * writing in HTML yourself. Unescaped '&' characters within * URLs will be properly escaped. Singlet tags such as br, img * and hr are checked for the '/' terminator (and it's added * if necessary). The best way to make sure you produce valid * XHTML with Textile is to not use any HTML markup at all-- * use the Textile syntax and let it produce the markup for you. * * @section license LICENSE * * Text::Textile is licensed under the same terms as Perl * itself. Textile.php is licensed under the terms of the GNU General * Public License. * * @section authorandcopyright AUTHOR & COPYRIGHT * * Text::Textile was written by Brad Choate, \. * It is an adaptation of Textile, developed by Dean Allen of Textism.com. * * Textile.php is a PHP port of Brad Choate's Text::Textile * (Textile.pm) Perl module. * * Textile.php was ported by Jim Riggs \. Great care has been taken to leave the Perl code in much the * same form as Textile.pm. While changes were required due to * syntactical differences between Perl and PHP, much of the code was * left intact (even if alternative syntax or code optimizations could * have been made in PHP), even to the point where one can compare * functions/subroutines side by side between the two implementations. * This has been done to ensure compatibility, reduce the possibility * of introducing errors, and simplify maintainance as one version or * the other is updated. * * @author Jim Riggs \ * @author Brad Choate \ * @copyright Copyright © 2004 Jim Riggs and Brad Choate * @version @(#) $Id: Textile.php,v 1.7 2004/06/03 13:54:47 jhriggs Exp $ */ ?>