From 5498206a5ad95b3d14cdbee0165a5db11d180d5a Mon Sep 17 00:00:00 2001 From: pecesama Date: Sat, 28 Apr 2007 03:14:25 +0000 Subject: [PATCH] Textile support --- classes/textile.class.php | 4080 +++++++++++++++++++++++++++++++++++++ index.php | 8 + 2 files changed, 4088 insertions(+) create mode 100644 classes/textile.class.php diff --git a/classes/textile.class.php b/classes/textile.class.php new file mode 100644 index 0000000..435eaef --- /dev/null +++ b/classes/textile.class.php @@ -0,0 +1,4080 @@ +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'] = (isset($this->options['char_encoding']) ? $this->options['char_encoding'] : 1); + $this->options['do_quotes'] = (isset($this->options['do_quotes']) ? $this->options['do_quotes'] : 1); + $this->options['trim_spaces'] = (isset($this->options['trim_spaces']) ? $this->options['trim_spaces'] : 0); + $this->options['smarty_mode'] = (isset($this->options['smarty_mode']) ? $this->options['smarty_mode'] : 1); + $this->options['preserve_spaces'] = (isset($this->options['preserve_spaces']) ? $this->options['preserve_spaaces'] : 0); + $this->options['head_offset'] = (isset($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 (isset($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'] && !isset($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'] && !isset($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" . $me->_repl($me->repl[0], $me->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('$me->_repl($me->repl[0], $m[1])'), $str); + + // preserve HTML comments + $str = preg_replace_callback('|()|s', $this->_cb('$me->_repl($me->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" . $me->_repl($me->repl[0], $m[1] . $me->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('$me->_repl($me->repl[0], $m[1] . $me->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" . $me->_repl($me->repl[0], $m[1] . $me->encode_html($m[2], 1) . $m[3]) . "\n\n"'), $str); + + // preserve PHPish, ASPish code + $str = preg_replace_callback('!(<([\?%]).*?(\2)>)!s', $this->_cb('$me->_repl($me->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('($me->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('$me->_repl($me->repl[0], $me->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 . '(?:>|>)!', str_replace('$', '\\$', $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 = (isset($args['text']) ? $args['text'] : ''); + + array_unshift($this->repl, array()); + $buffer = preg_replace_callback('{(?:^|(?<=[\s>])|([{[])) + ==(.+?)== + (?:$|([\]}])|(?=' . $this->punct . '{1,2}|\s))}sx', + $this->_cb('$me->_repl($me->repl[0], $me->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("||", str_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('$me->_repl($me->repl[0], $me->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('$me->_repl($me->repl[0], $me->format_image(array("pre" => $m[1], "src" => $m[5], "align" => ($m[2] ? $m[2] : $m[4]), "extra" => $m[6], "url" => $m[7], "clsty" => $m[3], "post" => $m[8])))'), $text); + + $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('$me->_repl($me->repl[0], $me->format_span(array("pre" => $m[1], "text" => $m[5], "align" => ($m[2] ? $m[2] : $m[4]), "cite" => $m[6], "clsty" => $m[3], "post" => $m[7])))'), $text); + + $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('$me->_repl($me->repl[0], $me->format_link(array("text" => $m[1], "linktext" => $m[3] . $m[6], "title" => $me->encode_html_basic($m[4] . $m[7]), "url" => $m[8], "clsty" => $m[2] . $m[5])))'), $text); + + $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('$me->_repl($me->repl[0], $me->format_link(array("text" => $m[1], "linktext" => $m[3] . $m[6], "title" => $me->encode_html_basic($m[4] . $m[7]), "url" => $m[8], "clsty" => $m[2] . $m[5])))'), $text); + + 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('$me->_repl($me->repl[0], $me->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('$me->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('$me->format_tag(array("tag" => end($me->tmp["r"]), "marker" => end($me->tmp["f"]), "pre" => $m[1], "text" => $m[3], "clsty" => $m[2], "post" => $m[4]))'), $text))) { + $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('$me->_repl($me->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] . $me->_repl($me->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("||", str_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 = (isset($args['pre']) ? $args['pre'] : ''); + $text = (isset($args['text']) ? $args['text'] : ''); + $cite = $args['cite']; + $post = (isset($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 = (isset($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 = (isset($args['text']) ? $args['text'] : ''); + $pre = (isset($args['pre']) ? $args['pre'] : ''); + $post = (isset($args['post']) ? $args['post'] : ''); + $clsty = (isset($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 = (isset($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 = (isset($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 = (isset($args['text']) ? $args['text'] : ''); + $inline = $args['inline']; + $pre = (isset($args['pre']) ? $args['pre'] : ''); + $post = (isset($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 = (isset($args['text']) ? $args['text'] : ''); + $linktext = (isset($args['linktext']) ? $args['linktext'] : ''); + $title = $args['title']; + $url = $args['url']; + $clsty = $args['clsty']; + + if (!$url || ($url == '')) { + return $text; + } + if (isset($this->links) && isset($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] . $me->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 = (isset($args['text']) ? $args['text'] : ''); + $pre = (isset($args['pre']) ? $args['pre'] : ''); + $post = (isset($args['post']) ? $args['post'] : ''); + $align = $args['align']; + $cite = (isset($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 = (isset($args['src']) ? $args['src'] : ''); + $extra = $args['extra']; + $align = $args['align']; + $pre = (isset($args['pre']) ? $args['pre'] : ''); + $post = (isset($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 = (isset($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] ? $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 ($rowspan > 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 (!isset($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.8", "build" => 2005032100); + } // 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', '$me =& 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.13 2005/03/21 15:26:55 jhriggs Exp $ + */ +?> diff --git a/index.php b/index.php index eb4ce10..ac1e09d 100644 --- a/index.php +++ b/index.php @@ -12,6 +12,7 @@ // My approach to MVC require(dirname(__FILE__)."/config.php"); include("classes/configuration.class.php"); + include("classes/textile.class.php"); include("classes/gelato.class.php"); include("classes/templates.class.php"); include("classes/pagination.php"); @@ -80,6 +81,9 @@ $formatedDate = date("M d", strtotime($register["date"])); $permalink = $conf->urlGelato."/index.php/post/".$register["id_post"]."/"; + $textile = new Textile; + $register["description"] = $textile->process(str_replace(""", "\"", $register["description"])); + switch ($tumble->getType($register["id_post"])) { case "1": $input = array("{Date_Added}", "{Permalink}", "{Title}", "{Body}", "{URL_Tumble}"); @@ -155,6 +159,10 @@ $formatedDate = date("M d", strtotime($register["date"])); $permalink = $conf->urlGelato."/index.php/post/".$register["id_post"]."/"; + + $textile = new Textile; + $register["description"] = $textile->process(str_replace(""", "\"", $register["description"])); + switch ($tumble->getType($register["id_post"])) { case "1": $input = array("{Date_Added}", "{Permalink}", "{Title}", "{Body}", "{URL_Tumble}");