| 1 | <?php |
|---|
| 2 | /** |
|---|
| 3 | * Wikka Formatting Engine |
|---|
| 4 | * |
|---|
| 5 | * This is the main formatting engine used by Wikka to parse wiki markup and render valid XHTML. |
|---|
| 6 | * |
|---|
| 7 | * @package Formatters |
|---|
| 8 | * @version $Id$ |
|---|
| 9 | * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License |
|---|
| 10 | * @filesource |
|---|
| 11 | * |
|---|
| 12 | * @author {@link http://wikkawiki.org/JsnX Jason Tourtelotte} |
|---|
| 13 | * @author {@link http://wikkawiki.org/DotMG Mahefa Randimbisoa} |
|---|
| 14 | * @author {@link http://wikkawiki.org/JavaWoman Marjolein Katsma} |
|---|
| 15 | * @author {@link http://wikkawiki.org/NilsLindenberg Nils Lindenberg} (code cleanup) |
|---|
| 16 | * @author {@link http://wikkawiki.org/DarTar Dario Taraborelli} (grab handler and filename support for codeblocks) |
|---|
| 17 | * @author {@link http://wikkawiki.org/TormodHaugen Tormod Haugen} (table formatter support) |
|---|
| 18 | * |
|---|
| 19 | * @uses Wakka::htmlspecialchars_ent() |
|---|
| 20 | * |
|---|
| 21 | * @todo add support for formatter plugins; |
|---|
| 22 | * @todo use a central RegEx library #34; |
|---|
| 23 | * @todo add further improvements from ImprovedFormatter |
|---|
| 24 | */ |
|---|
| 25 | |
|---|
| 26 | /**#@+ |
|---|
| 27 | * Code block pattern. |
|---|
| 28 | */ |
|---|
| 29 | if (!defined('PATTERN_OPEN_BRACKET')) define('PATTERN_OPEN_BRACKET', '\('); |
|---|
| 30 | if (!defined('PATTERN_FORMATTER')) define('PATTERN_FORMATTER', '([^;\)]+)'); |
|---|
| 31 | if (!defined('PATTERN_LINE_NUMBER')) define('PATTERN_LINE_NUMBER', '(;(\d*?))?'); |
|---|
| 32 | if (!defined('PATTERN_FILENAME')) define('PATTERN_FILENAME', '(;([^\)\x01-\x1f\*\?\"<>\|]*)([^\)]*))?'); |
|---|
| 33 | if (!defined('PATTERN_CLOSE_BRACKET')) define('PATTERN_CLOSE_BRACKET', '\)'); |
|---|
| 34 | if (!defined('PATTERN_CODE')) define('PATTERN_CODE', '(.*)'); |
|---|
| 35 | /**#@-*/ |
|---|
| 36 | |
|---|
| 37 | /** |
|---|
| 38 | * Match heading tags. |
|---|
| 39 | * |
|---|
| 40 | * - $result[0] : the entire node representation, including the closing tag |
|---|
| 41 | * - $result[1] : the nodename (h1, h2, .. , h6) |
|---|
| 42 | * - $result[2] : the heading attribute, ie all the strings after the tagname and before the first ">" character |
|---|
| 43 | * - $result[3] : the content of the heading tag, just like the innerHTML method in DOM. |
|---|
| 44 | * This pattern will match only if the text it is applied to is valid XHTML: it should use lowercase in the tagName, |
|---|
| 45 | * it should not contain the character ">" inside attributes. |
|---|
| 46 | */ |
|---|
| 47 | if (!defined('PATTERN_MATCH_HEADINGS')) define('PATTERN_MATCH_HEADINGS', '#^<(h[1-6])(.*?)>(.*?)</\\1>$#s'); |
|---|
| 48 | /** |
|---|
| 49 | * Match id in attributes. |
|---|
| 50 | * |
|---|
| 51 | * - $result[0] : a string like <code>id="h1_id"</code>, starting with the letters id=, and followed by a string |
|---|
| 52 | * enclosed in either single or double quote. It doesn't match if the term id is not preceded by any whitespace. |
|---|
| 53 | * - $result[1] : The single character used to enclose the string, either a single or a double quote. |
|---|
| 54 | * - $result[2] : The content of the string, ie the value of the id attribute. |
|---|
| 55 | * The RE uses a backref to match both single and double enclosing quotes. |
|---|
| 56 | */ |
|---|
| 57 | if (!defined('PATTERN_MATCH_ID_ATTRIBUTES')) define('PATTERN_MATCH_ID_ATTRIBUTES', '/(?<=\\s)id=("|\')(.*?)\\1/'); |
|---|
| 58 | /** |
|---|
| 59 | * The string $format_option is a semicolon separated list of strings, including the word `page' |
|---|
| 60 | */ |
|---|
| 61 | if (!defined('PATTERN_MATCH_PAGE_FORMATOPTION')) define('PATTERN_MATCH_PAGE_FORMATOPTION', '/(^|;)page(;|$)/'); |
|---|
| 62 | |
|---|
| 63 | // @@@ is this condition handy? would prevent generating IDs on a page fragment |
|---|
| 64 | // - unless formatter is called on those with an explicit foprmat option! |
|---|
| 65 | if (isset($format_option) && preg_match(PATTERN_MATCH_PAGE_FORMATOPTION, $format_option)) |
|---|
| 66 | { |
|---|
| 67 | if (!function_exists('wakka3callback')) |
|---|
| 68 | { |
|---|
| 69 | /** |
|---|
| 70 | * "Afterburner" formatting: extra handling of already-generated XHTML code. |
|---|
| 71 | * |
|---|
| 72 | * 1. For headings: |
|---|
| 73 | * a) Use heading to derive a document title |
|---|
| 74 | * b) Ensure every heading has an id, either specified or generated. (May be |
|---|
| 75 | * extended to generate section TOC data.) |
|---|
| 76 | * If an id is already specified, that is used without any modification. |
|---|
| 77 | * If no id is specified, it is generated on the basis of the heading context: |
|---|
| 78 | * - any image tag is replaced by its alt text (if specified) |
|---|
| 79 | * - all tags are stripped |
|---|
| 80 | * - all characters that are not valid in an ID are stripped (except whitespace) |
|---|
| 81 | * - the resulting string is then used by makedId() to generate an id out of it |
|---|
| 82 | * |
|---|
| 83 | * @access private |
|---|
| 84 | * @uses Wakka::HasPageTitle() |
|---|
| 85 | * @uses Wakka::SetPageTitle() |
|---|
| 86 | * @uses Wakka::CleanTextNode() |
|---|
| 87 | * @uses Wakka::makeId() |
|---|
| 88 | * |
|---|
| 89 | * @param array $things required: matches of the regex in the preg_replace_callback |
|---|
| 90 | * @return string heading with an id attribute |
|---|
| 91 | */ |
|---|
| 92 | function wakka3callback($things) |
|---|
| 93 | { |
|---|
| 94 | global $wakka; |
|---|
| 95 | $thing = $things[1]; |
|---|
| 96 | |
|---|
| 97 | // heading |
|---|
| 98 | if (preg_match(PATTERN_MATCH_HEADINGS, $thing, $matches)) |
|---|
| 99 | { |
|---|
| 100 | list($h_element, $h_tagname, $h_attribs, $h_heading) = $matches; |
|---|
| 101 | // @@@ apply nodeToTextOnly() on $h_heading so stored title is always valid |
|---|
| 102 | if ((!$wakka->HasPageTitle()) && ('h5' > $h_tagname)) |
|---|
| 103 | { |
|---|
| 104 | $wakka->SetPageTitle($h_heading); |
|---|
| 105 | } |
|---|
| 106 | |
|---|
| 107 | if (preg_match(PATTERN_MATCH_ID_ATTRIBUTES, $h_attribs)) |
|---|
| 108 | { |
|---|
| 109 | // existing id attribute: nothing to do (assume already treated as embedded code) |
|---|
| 110 | // @@@ we *may* want to gather ids and heading text for a TOC here ... |
|---|
| 111 | // heading text should then get partly the same treatment as when we're creating ids: |
|---|
| 112 | // at least replace images and strip tags - we can leave entities etc. alone - so we end up with |
|---|
| 113 | // plain text-only |
|---|
| 114 | // do this if we have a condition set to generate a TOC |
|---|
| 115 | return $h_element; |
|---|
| 116 | } |
|---|
| 117 | else |
|---|
| 118 | { |
|---|
| 119 | // no id: we'll have to create one |
|---|
| 120 | $headingtext = $wakka->CleanTextNode($h_heading); // @@@ replace with headingToTextOnly() |
|---|
| 121 | // now create id based on resulting heading text |
|---|
| 122 | $h_id = $wakka->makeId('hn', $headingtext); |
|---|
| 123 | |
|---|
| 124 | // rebuild element, adding id |
|---|
| 125 | return '<'.$h_tagname.$h_attribs.' id="'.$h_id.'">'.$h_heading.'</'.$h_tagname.'>'; |
|---|
| 126 | } |
|---|
| 127 | } |
|---|
| 128 | // other elements to be treated go here (tables, images, code sections...) |
|---|
| 129 | } |
|---|
| 130 | } |
|---|
| 131 | } |
|---|
| 132 | // Note: all possible formatting tags have to be in a single regular expression for this to work correctly. |
|---|
| 133 | |
|---|
| 134 | if (!function_exists("wakka2callback")) # DotMG [many lines] : Unclosed tags fix! |
|---|
| 135 | # JW: NOT a complete fix! |
|---|
| 136 | # see http://wikkawiki.org/ImprovedFormatter#hn_Not_a_complete_solution |
|---|
| 137 | { |
|---|
| 138 | function wakka2callback($things) |
|---|
| 139 | { |
|---|
| 140 | $thing = $things[0]; |
|---|
| 141 | $result=''; |
|---|
| 142 | |
|---|
| 143 | static $oldIndentLevel = 0; |
|---|
| 144 | static $oldIndentLength= 0; |
|---|
| 145 | static $indentClosers = array(); |
|---|
| 146 | static $newIndentSpace= array(); |
|---|
| 147 | static $br = 1; |
|---|
| 148 | static $trigger_table = 0; |
|---|
| 149 | static $trigger_rowgroup = 0; |
|---|
| 150 | static $trigger_colgroup = 0; |
|---|
| 151 | static $trigger_bold = 0; |
|---|
| 152 | static $trigger_italic = 0; |
|---|
| 153 | static $trigger_underline = 0; |
|---|
| 154 | static $trigger_monospace = 0; |
|---|
| 155 | static $trigger_notes = 0; |
|---|
| 156 | static $trigger_strike = 0; |
|---|
| 157 | static $trigger_inserted = 0; |
|---|
| 158 | static $trigger_deleted = 0; |
|---|
| 159 | static $trigger_floatl = 0; |
|---|
| 160 | static $trigger_floatr = 0; |
|---|
| 161 | static $trigger_keys = 0; |
|---|
| 162 | static $trigger_strike = 0; |
|---|
| 163 | static $trigger_inserted = 0; |
|---|
| 164 | static $trigger_center = 0; |
|---|
| 165 | static $trigger_l = array(-1, 0, 0, 0, 0, 0); |
|---|
| 166 | static $output = ''; |
|---|
| 167 | static $valid_filename = ''; |
|---|
| 168 | static $invalid = ''; |
|---|
| 169 | static $curIndentType; |
|---|
| 170 | |
|---|
| 171 | global $wakka; |
|---|
| 172 | |
|---|
| 173 | // @@@ inline elements should be closed before block-level elements |
|---|
| 174 | // (see ImprovedFormatter solution again) |
|---|
| 175 | // TEST: are indents closed at end of page now??? (see... again) |
|---|
| 176 | // @@@ <kbd> is missing |
|---|
| 177 | if ((!is_array($things)) && ($things == 'closetags')) |
|---|
| 178 | { |
|---|
| 179 | $result = ''; |
|---|
| 180 | if (3 < $trigger_table){ |
|---|
| 181 | $result .= '</caption>'; |
|---|
| 182 | } |
|---|
| 183 | elseif (2 < $trigger_table) |
|---|
| 184 | { |
|---|
| 185 | $result .= '</th></tr>'; |
|---|
| 186 | } |
|---|
| 187 | elseif (1 < $trigger_table) |
|---|
| 188 | { |
|---|
| 189 | $result .= '</td></tr>'; |
|---|
| 190 | } |
|---|
| 191 | if (2 < $trigger_rowgroup) |
|---|
| 192 | { |
|---|
| 193 | $result .= '</tbody>'; |
|---|
| 194 | } |
|---|
| 195 | elseif (1 < $trigger_rowgroup) |
|---|
| 196 | { |
|---|
| 197 | $result .= '</tfoot>'; |
|---|
| 198 | } |
|---|
| 199 | elseif (0 < $trigger_rowgroup) |
|---|
| 200 | { |
|---|
| 201 | $result .= '</thead>'; |
|---|
| 202 | } |
|---|
| 203 | if (0 < $trigger_table) |
|---|
| 204 | { |
|---|
| 205 | $result .= '</table>'; |
|---|
| 206 | } |
|---|
| 207 | |
|---|
| 208 | if ($trigger_strike % 2) |
|---|
| 209 | { |
|---|
| 210 | $result .= '</span>'; |
|---|
| 211 | } |
|---|
| 212 | if ($trigger_notes % 2) |
|---|
| 213 | { |
|---|
| 214 | $result .= '</span>'; |
|---|
| 215 | } |
|---|
| 216 | if ($trigger_inserted % 2) |
|---|
| 217 | { |
|---|
| 218 | $result .= '</span>'; |
|---|
| 219 | } |
|---|
| 220 | if ($trigger_underline % 2) |
|---|
| 221 | { |
|---|
| 222 | $result .= '</span>'; |
|---|
| 223 | } |
|---|
| 224 | if ($trigger_floatl % 2) |
|---|
| 225 | { |
|---|
| 226 | $result .= '</div>'; |
|---|
| 227 | } |
|---|
| 228 | if ($trigger_floatr % 2) |
|---|
| 229 | { |
|---|
| 230 | $result .= '</div>'; |
|---|
| 231 | } |
|---|
| 232 | if ($trigger_center % 2) |
|---|
| 233 | { |
|---|
| 234 | $result .= '</div>'; |
|---|
| 235 | } |
|---|
| 236 | if ($trigger_italic % 2) |
|---|
| 237 | { |
|---|
| 238 | $result .= '</em>'; |
|---|
| 239 | } |
|---|
| 240 | if ($trigger_monospace % 2) |
|---|
| 241 | { |
|---|
| 242 | $result .= '</tt>'; |
|---|
| 243 | } |
|---|
| 244 | if ($trigger_bold % 2) |
|---|
| 245 | { |
|---|
| 246 | $result .= '</strong>'; |
|---|
| 247 | } |
|---|
| 248 | |
|---|
| 249 | for ($i = 1; $i<=5; $i ++) |
|---|
| 250 | { |
|---|
| 251 | if ($trigger_l[$i] % 2) $result .= "</h$i>"; |
|---|
| 252 | } |
|---|
| 253 | |
|---|
| 254 | $trigger_bold = $trigger_center = $trigger_floatl = $trigger_floatr = $trigger_inserted = $trigger_deleted = $trigger_italic = $trigger_keys = $trigger_table = 0; |
|---|
| 255 | $trigger_l = array(-1, 0, 0, 0, 0, 0); |
|---|
| 256 | $trigger_monospace = $trigger_notes = $trigger_strike = $trigger_underline = 0; |
|---|
| 257 | return $result; |
|---|
| 258 | } |
|---|
| 259 | // Ignore the closing delimiter if there is nothing to close. |
|---|
| 260 | elseif ( preg_match("/^\|\|\n$/", $thing, $matches) && $trigger_table == 1 ) |
|---|
| 261 | { |
|---|
| 262 | return ''; |
|---|
| 263 | } |
|---|
| 264 | |
|---|
| 265 | // $matches[1] is element, $matches[2] is attributes, $matches[3] is styles and $matches[4] is linebreak |
|---|
| 266 | elseif ( preg_match("/^\|([^\|])?\|(\(.*?\))?(\{.*?\})?(\n)?$/", $thing, $matches) ) |
|---|
| 267 | { |
|---|
| 268 | for ( $i = 1; $i < 5; $i++ ) #38 |
|---|
| 269 | { |
|---|
| 270 | if (!isset($matches[$i])) $matches[$i] = ''; |
|---|
| 271 | } |
|---|
| 272 | //Set up the variables that will aggregate the html markup |
|---|
| 273 | $close_part = ''; |
|---|
| 274 | $open_part = ''; |
|---|
| 275 | $linebreak_after_open = ''; |
|---|
| 276 | $selfclose = ''; |
|---|
| 277 | |
|---|
| 278 | // $trigger_table == 0 means no table, 1 means in table but no cell, 2 is in datacell, 3 is in headercell, 4 is in caption. |
|---|
| 279 | |
|---|
| 280 | //If we have parsed the caption, close it, set trigger = 1 and return. |
|---|
| 281 | if ( $trigger_table == 4 ) |
|---|
| 282 | { |
|---|
| 283 | $close_part = '</caption>'."\n"; |
|---|
| 284 | $trigger_table = 1; |
|---|
| 285 | return $close_part; |
|---|
| 286 | } |
|---|
| 287 | |
|---|
| 288 | //If we have parsed a cell - close it, go on to open new. |
|---|
| 289 | if ( $trigger_table == 3 ) |
|---|
| 290 | { |
|---|
| 291 | $close_part = '</th>'; |
|---|
| 292 | } |
|---|
| 293 | elseif ( $trigger_table == 2 ) |
|---|
| 294 | { |
|---|
| 295 | $close_part = '</td>'; |
|---|
| 296 | } |
|---|
| 297 | // If no cell, or we want to open a table; then there is nothing to close |
|---|
| 298 | elseif ( $trigger_table == 1 || $matches[1] == '!') |
|---|
| 299 | { |
|---|
| 300 | $close_part = ''; |
|---|
| 301 | } |
|---|
| 302 | else |
|---|
| 303 | { |
|---|
| 304 | //This is actually opening the table (i.e. nothing at all to close). Go on to open a cell. |
|---|
| 305 | $trigger_table = 1; |
|---|
| 306 | $close_part = '<table class="data">'."\n"; |
|---|
| 307 | } |
|---|
| 308 | |
|---|
| 309 | //If we are in a cell and there is a linebreak - then it is end of row. |
|---|
| 310 | if ( $trigger_table > 1 && $matches[4] == "\n" ) |
|---|
| 311 | { |
|---|
| 312 | $trigger_table = 1; |
|---|
| 313 | return $close_part .= '</tr>'."\n"; //Can return here, it is closed- |
|---|
| 314 | } |
|---|
| 315 | |
|---|
| 316 | //If we were in a colgroup and there is a linebreak, then it is the end. |
|---|
| 317 | if ( $trigger_colgroup == 1 && $matches[4] == "\n" ) |
|---|
| 318 | { |
|---|
| 319 | $trigger_colgroup = 0; |
|---|
| 320 | return $close_part .= '</colgroup>'."\n"; //Can return here, it is closed- |
|---|
| 321 | } |
|---|
| 322 | |
|---|
| 323 | //We want to start a new table, and most likely have attributes to parse. |
|---|
| 324 | //TODO: Need to find out if class="data" should be auto added, and if so - put it in the attribute list to add up. |
|---|
| 325 | if ( $matches[1] == '!' ) |
|---|
| 326 | { |
|---|
| 327 | $trigger_table = 1; |
|---|
| 328 | $open_part = '<table class="data"'; |
|---|
| 329 | $linebreak_after_open = "\n"; |
|---|
| 330 | } |
|---|
| 331 | //Open a caption. |
|---|
| 332 | elseif ( $matches[1] == '?' ) |
|---|
| 333 | { |
|---|
| 334 | $trigger_table = 4; |
|---|
| 335 | $open_part = '<caption'; |
|---|
| 336 | } |
|---|
| 337 | //Start a rowgroup. |
|---|
| 338 | elseif ( $matches[1] == '#' || $matches[1] == '[' || $matches[1] == ']' ) |
|---|
| 339 | { |
|---|
| 340 | //If we're here, we want to close any open rowgroup. |
|---|
| 341 | if (2 < $trigger_rowgroup) |
|---|
| 342 | { |
|---|
| 343 | $close_part .= '</tbody>'."\n"; |
|---|
| 344 | } |
|---|
| 345 | elseif (1 < $trigger_rowgroup) |
|---|
| 346 | { |
|---|
| 347 | $close_part .= '</tfoot>'."\n"; |
|---|
| 348 | } |
|---|
| 349 | elseif (0 < $trigger_rowgroup) |
|---|
| 350 | { |
|---|
| 351 | $close_part .= '</thead>'."\n"; |
|---|
| 352 | } |
|---|
| 353 | |
|---|
| 354 | //Then open the appropriate rowgroup. |
|---|
| 355 | if ($matches[1] == '[' ) |
|---|
| 356 | { |
|---|
| 357 | $open_part .= '<thead'; |
|---|
| 358 | $trigger_rowgroup = 1; |
|---|
| 359 | } |
|---|
| 360 | elseif ($matches[1] == ']' ) |
|---|
| 361 | { |
|---|
| 362 | $open_part .= '<tfoot'; |
|---|
| 363 | $trigger_rowgroup = 2; |
|---|
| 364 | } |
|---|
| 365 | else |
|---|
| 366 | { |
|---|
| 367 | $open_part .= '<tbody'; |
|---|
| 368 | $trigger_rowgroup = 3; |
|---|
| 369 | } |
|---|
| 370 | |
|---|
| 371 | $linebreak_after_open = "\n"; |
|---|
| 372 | } |
|---|
| 373 | //Here we want to add colgroup. |
|---|
| 374 | elseif ( $matches[1] == '_' ) |
|---|
| 375 | { |
|---|
| 376 | //close any open colgroup |
|---|
| 377 | if ( $trigger_colgroup == 1 ) |
|---|
| 378 | { |
|---|
| 379 | $close_part .= '</colgroup>'."\n"; |
|---|
| 380 | } |
|---|
| 381 | |
|---|
| 382 | $trigger_colgroup = 1; |
|---|
| 383 | $open_part .= '<colgroup'; |
|---|
| 384 | } |
|---|
| 385 | //And col elements |
|---|
| 386 | elseif ( $matches[1] == '-' ) |
|---|
| 387 | { |
|---|
| 388 | $open_part .= '<col'; |
|---|
| 389 | $selfclose = ' /'; |
|---|
| 390 | if ( $matches[4] ) $linebreak_after_open = "\n"; |
|---|
| 391 | } |
|---|
| 392 | //Ok, then it is cells. |
|---|
| 393 | else |
|---|
| 394 | { |
|---|
| 395 | $open_part = ''; |
|---|
| 396 | //Need a tbody if no other rowgroup open. |
|---|
| 397 | if ($trigger_rowgroup == 0) |
|---|
| 398 | { |
|---|
| 399 | $open_part .= '<tbody>'."\n"; |
|---|
| 400 | $trigger_rowgroup = 3; |
|---|
| 401 | } |
|---|
| 402 | |
|---|
| 403 | //If no row, open a new one. |
|---|
| 404 | if ( $trigger_table == 1 ) |
|---|
| 405 | { |
|---|
| 406 | $open_part .= '<tr>'; |
|---|
| 407 | } |
|---|
| 408 | |
|---|
| 409 | //Header cell. |
|---|
| 410 | if ( $matches[1] == '=' ) |
|---|
| 411 | { |
|---|
| 412 | $trigger_table = 3; |
|---|
| 413 | $open_part .= '<th'; |
|---|
| 414 | } |
|---|
| 415 | //Datacell |
|---|
| 416 | else |
|---|
| 417 | { |
|---|
| 418 | $trigger_table = 2; |
|---|
| 419 | $open_part .= '<td'; |
|---|
| 420 | } |
|---|
| 421 | } |
|---|
| 422 | |
|---|
| 423 | //If attributes... |
|---|
| 424 | if ( preg_match("/\((.*)\)/", $matches[2], $attribs ) ) |
|---|
| 425 | { |
|---|
| 426 | // $hints = array('core' => 'core', 'i18n' => 'i18n'); |
|---|
| 427 | $hints = array(); |
|---|
| 428 | //allow / disallow different attribute keys. (ie. data/header cell only. |
|---|
| 429 | if ($trigger_table == 2 || $trigger_table == 3) |
|---|
| 430 | { |
|---|
| 431 | $hints['cell'] = 'cell'; |
|---|
| 432 | } |
|---|
| 433 | else |
|---|
| 434 | { |
|---|
| 435 | $hints['other_table'] = 'other_table'; |
|---|
| 436 | } |
|---|
| 437 | $open_part .= parse_attributes($attribs[1], $hints); |
|---|
| 438 | } |
|---|
| 439 | |
|---|
| 440 | //If styles, just make attribute of it and parse again. |
|---|
| 441 | if ( preg_match("/\{(.*)\}/", $matches[3], $attribs ) ) |
|---|
| 442 | { |
|---|
| 443 | $attribs = "s:".$attribs[1]; |
|---|
| 444 | $open_part .= parse_attributes($attribs, array() ); |
|---|
| 445 | } |
|---|
| 446 | |
|---|
| 447 | //the variable $selfclose is "/" if this is a <col/> element. |
|---|
| 448 | $open_part .= $selfclose.'>'; |
|---|
| 449 | return $close_part . $open_part . $linebreak_after_open; |
|---|
| 450 | } |
|---|
| 451 | //Are in table, no cell - but not asked to open new: please close and parse again. ;) |
|---|
| 452 | else if ( $trigger_table == 1 ) |
|---|
| 453 | { |
|---|
| 454 | $close_part = ''; |
|---|
| 455 | if (2 < $trigger_rowgroup) |
|---|
| 456 | { |
|---|
| 457 | $close_part .= '</tbody>'."\n"; |
|---|
| 458 | } |
|---|
| 459 | elseif (1 < $trigger_rowgroup) |
|---|
| 460 | { |
|---|
| 461 | $close_part .= '</tfoot>'."\n"; |
|---|
| 462 | } |
|---|
| 463 | elseif (0 < $trigger_rowgroup) |
|---|
| 464 | { |
|---|
| 465 | $close_part .= '</thead>'."\n"; |
|---|
| 466 | } |
|---|
| 467 | |
|---|
| 468 | $close_part .= '</table>'."\n"; |
|---|
| 469 | |
|---|
| 470 | $trigger_table = $trigger_rowgroup = 0; |
|---|
| 471 | |
|---|
| 472 | //And remember to parse what we got. |
|---|
| 473 | return $close_part.wakka2callback($things); |
|---|
| 474 | } |
|---|
| 475 | |
|---|
| 476 | // convert HTML thingies |
|---|
| 477 | if ($thing == "<") |
|---|
| 478 | { |
|---|
| 479 | return "<"; |
|---|
| 480 | } |
|---|
| 481 | elseif ($thing == ">") |
|---|
| 482 | { |
|---|
| 483 | return ">"; |
|---|
| 484 | } |
|---|
| 485 | // float box left |
|---|
| 486 | elseif ($thing == "<<") |
|---|
| 487 | { |
|---|
| 488 | return (++$trigger_floatl % 2 ? '<div class="floatl">' : '</div>'); |
|---|
| 489 | } |
|---|
| 490 | // float box right |
|---|
| 491 | elseif ($thing == ">>") |
|---|
| 492 | { |
|---|
| 493 | return (++$trigger_floatr % 2 ? '<div class="floatr">' : '</div>'); |
|---|
| 494 | } |
|---|
| 495 | // clear floated element |
|---|
| 496 | elseif ($thing == "::c::") |
|---|
| 497 | { |
|---|
| 498 | return ("<div class=\"clear\"> </div>\n"); |
|---|
| 499 | } |
|---|
| 500 | // keyboard |
|---|
| 501 | elseif ($thing == "#%") |
|---|
| 502 | { |
|---|
| 503 | return (++$trigger_keys % 2 ? "<kbd class=\"keys\">" : "</kbd>"); |
|---|
| 504 | } |
|---|
| 505 | // bold |
|---|
| 506 | elseif ($thing == "**") |
|---|
| 507 | { |
|---|
| 508 | return (++$trigger_bold % 2 ? "<strong>" : "</strong>"); |
|---|
| 509 | } |
|---|
| 510 | // italic |
|---|
| 511 | elseif ($thing == "//") |
|---|
| 512 | { |
|---|
| 513 | return (++$trigger_italic % 2 ? "<em>" : "</em>"); |
|---|
| 514 | } |
|---|
| 515 | // underlinue |
|---|
| 516 | elseif ($thing == "__") |
|---|
| 517 | { |
|---|
| 518 | return (++$trigger_underline % 2 ? "<span class=\"underline\">" : "</span>"); |
|---|
| 519 | } |
|---|
| 520 | // monospace |
|---|
| 521 | elseif ($thing == "##") |
|---|
| 522 | { |
|---|
| 523 | return (++$trigger_monospace % 2 ? "<tt>" : "</tt>"); |
|---|
| 524 | } |
|---|
| 525 | // notes |
|---|
| 526 | elseif ($thing == "''") |
|---|
| 527 | { |
|---|
| 528 | return (++$trigger_notes % 2 ? "<span class=\"notes\">" : "</span>"); |
|---|
| 529 | } |
|---|
| 530 | // strikethrough |
|---|
| 531 | elseif ($thing == "++") |
|---|
| 532 | { |
|---|
| 533 | return (++$trigger_strike % 2 ? "<span class=\"strikethrough\">" : "</span>"); |
|---|
| 534 | } |
|---|
| 535 | // additions |
|---|
| 536 | elseif ($thing == "££") |
|---|
| 537 | { |
|---|
| 538 | return (++$trigger_inserted % 2 ? "<ins>" : "</ins>"); |
|---|
| 539 | } |
|---|
| 540 | // deletions |
|---|
| 541 | elseif ($thing == "¥¥") |
|---|
| 542 | { |
|---|
| 543 | return (++$trigger_deleted % 2 ? "<del>" : "</del>"); |
|---|
| 544 | } |
|---|
| 545 | // center |
|---|
| 546 | elseif ($thing == "@@") |
|---|
| 547 | { |
|---|
| 548 | return (++$trigger_center % 2 ? "<div class=\"center\">\n" : "\n</div>\n"); |
|---|
| 549 | } |
|---|
| 550 | // urls |
|---|
| 551 | elseif (preg_match("/^([a-z]+:\/\/\S+?)([^[:alnum:]^\/])?$/", $thing, $matches)) |
|---|
| 552 | { |
|---|
| 553 | $url = $matches[1]; |
|---|
| 554 | /* Inline images are disabled for security reason, use {{image action}} #142 |
|---|
| 555 | But if you still need this functionality, update this file like below |
|---|
| 556 | if (preg_match("/\.(gif|jpg|png|svg)$/si", $url)) { |
|---|
| 557 | return '<img src="'.$wakka->Link($url).'" alt="image" />'.$wakka->htmlspecialchars_ent($matches[2]); |
|---|
| 558 | } else */ |
|---|
| 559 | // Mind Mapping Mod |
|---|
| 560 | if (preg_match("/\.(mm)$/si", $url)) { #145 |
|---|
| 561 | return $wakka->Action("mindmap ".$url); |
|---|
| 562 | } else |
|---|
| 563 | return $wakka->Link($url).(isset($matches[2]) ? $matches[2] : ''); #38 |
|---|
| 564 | } |
|---|
| 565 | // header level 5 |
|---|
| 566 | elseif ($thing == "==") |
|---|
| 567 | { |
|---|
| 568 | $br = 0; |
|---|
| 569 | return (++$trigger_l[5] % 2 ? "<h5>" : "</h5>\n"); |
|---|
| 570 | } |
|---|
| 571 | // header level 4 |
|---|
| 572 | elseif ($thing == "===") |
|---|
| 573 | { |
|---|
| 574 | $br = 0; |
|---|
| 575 | return (++$trigger_l[4] % 2 ? "<h4>" : "</h4>\n"); |
|---|
| 576 | } |
|---|
| 577 | // header level 3 |
|---|
| 578 | elseif ($thing == "====") |
|---|
| 579 | { |
|---|
| 580 | $br = 0; |
|---|
| 581 | return (++$trigger_l[3] % 2 ? "<h3>" : "</h3>\n"); |
|---|
| 582 | } |
|---|
| 583 | // header level 2 |
|---|
| 584 | elseif ($thing == "=====") |
|---|
| 585 | { |
|---|
| 586 | $br = 0; |
|---|
| 587 | return (++$trigger_l[2] % 2 ? "<h2>" : "</h2>\n"); |
|---|
| 588 | } |
|---|
| 589 | // header level 1 |
|---|
| 590 | elseif ($thing == "======") |
|---|
| 591 | { |
|---|
| 592 | $br = 0; |
|---|
| 593 | return (++$trigger_l[1] % 2 ? "<h1>" : "</h1>\n"); |
|---|
| 594 | } |
|---|
| 595 | // forced line breaks |
|---|
| 596 | elseif ($thing == "---") |
|---|
| 597 | { |
|---|
| 598 | return "<br />"; |
|---|
| 599 | } |
|---|
| 600 | // escaped text |
|---|
| 601 | elseif (preg_match("/^\"\"(.*)\"\"$/s", $thing, $matches)) |
|---|
| 602 | { |
|---|
| 603 | $ddquotes_policy = $wakka->GetConfigValue("double_doublequote_html"); |
|---|
| 604 | $embedded = $matches[1]; |
|---|
| 605 | if (($ddquotes_policy == 'safe') || ($ddquotes_policy == 'raw')) |
|---|
| 606 | { |
|---|
| 607 | // get tags with id attributes |
|---|
| 608 | # use backref to match both single and double quotes |
|---|
| 609 | $patTagWithId = '((<[a-z][^>]*)((?<=\\s)id=("|\')(.*?)\\4)(.*?>))'; // @@@ #34 |
|---|
| 610 | // with PREG_SET_ORDER we get an array for each match: easy to use with list()! |
|---|
| 611 | // we do the match case-insensitive so we catch uppercase HTML as well; |
|---|
| 612 | // SafeHTML will treat this but 'raw' may end up with invalid code! |
|---|
| 613 | $tags2 = preg_match_all('/'.$patTagWithId.'/i', $embedded, $matches2, PREG_SET_ORDER); |
|---|
| 614 | // step through code, replacing tags with ids with tags with new ('repaired') ids |
|---|
| 615 | $tmpembedded = $embedded; |
|---|
| 616 | $newembedded = ''; |
|---|
| 617 | for ($i=0; $i < $tags2; $i++) |
|---|
| 618 | { |
|---|
| 619 | list( , $tag, $tagstart, $attrid, $quote, $id, $tagend) = $matches2[$i]; # $attrid not needed, just for clarity |
|---|
| 620 | $parts = explode($tag, $tmpembedded, 2); # split in two at matched tag |
|---|
| 621 | if ($id != ($newid = $wakka->makeId('embed', $id))) # replace if we got a new value |
|---|
| 622 | { |
|---|
| 623 | $tag = $tagstart.'id='.$quote.$newid.$quote.$tagend; |
|---|
| 624 | } |
|---|
| 625 | $newembedded .= $parts[0].$tag; # append (replacement) tag to first part |
|---|
| 626 | $tmpembedded = $parts[1]; # after tag: next bit to handle |
|---|
| 627 | } |
|---|
| 628 | $newembedded .= $tmpembedded; # add last part |
|---|
| 629 | } |
|---|
| 630 | switch ($ddquotes_policy) |
|---|
| 631 | { |
|---|
| 632 | case 'safe': |
|---|
| 633 | return $wakka->ReturnSafeHTML($newembedded); |
|---|
| 634 | case 'raw': |
|---|
| 635 | return $newembedded; # may still be invalid code - 'raw' will not be corrected! |
|---|
| 636 | default: |
|---|
| 637 | return $wakka->htmlspecialchars_ent($embedded); # display only |
|---|
| 638 | } |
|---|
| 639 | } |
|---|
| 640 | // code text |
|---|
| 641 | elseif (preg_match("/^%%(.*?)%%$/s", $thing, $matches)) |
|---|
| 642 | { |
|---|
| 643 | /* |
|---|
| 644 | * Note: this routine is rewritten such that (new) language formatters |
|---|
| 645 | * will automatically be found, whether they are GeSHi language config files |
|---|
| 646 | * or "internal" Wikka formatters. |
|---|
| 647 | * Path to GeSHi language files and Wikka formatters MUST be defined in config. |
|---|
| 648 | * For line numbering (GeSHi only) a starting line can be specified after the language |
|---|
| 649 | * code, separated by a ; e.g., %%(php;27)....%%. |
|---|
| 650 | * Specifying >= 1 turns on line numbering if this is enabled in the configuration. |
|---|
| 651 | * An optional filename can be specified as well, e.g. %%(php;27;myfile.php)....%% |
|---|
| 652 | * This filename will be used by the grabcode handler. |
|---|
| 653 | */ |
|---|
| 654 | $output = ''; //reinitialize variables |
|---|
| 655 | $filename = ''; |
|---|
| 656 | $valid_filename = ''; |
|---|
| 657 | $code = $matches[1]; |
|---|
| 658 | // if configuration path isn't set, make sure we'll get an invalid path so we |
|---|
| 659 | // don't match anything in the home directory |
|---|
| 660 | $geshi_hi_path = isset($wakka->config['geshi_languages_path']) ? $wakka->config['geshi_languages_path'] : '/:/'; |
|---|
| 661 | $wikka_hi_path = isset($wakka->config['wikka_highlighters_path']) ? $wakka->config['wikka_highlighters_path'] : '/:/'; |
|---|
| 662 | // check if a language (and an optional starting line or filename) has been specified |
|---|
| 663 | if (preg_match('/^'.PATTERN_OPEN_BRACKET.PATTERN_FORMATTER.PATTERN_LINE_NUMBER.PATTERN_FILENAME.PATTERN_CLOSE_BRACKET.PATTERN_CODE.'$/s', $code, $matches)) |
|---|
| 664 | { |
|---|
| 665 | list(, $language, , $start, , $filename, $invalid, $code) = $matches; |
|---|
| 666 | } |
|---|
| 667 | // get rid of newlines at start and end (and preceding/following whitespace) |
|---|
| 668 | // Note: unlike trim(), this preserves any tabs at the start of the first "real" line |
|---|
| 669 | $code = preg_replace('/^\s*\n+|\n+\s*$/','',$code); |
|---|
| 670 | |
|---|
| 671 | // check if GeSHi path is set and we have a GeSHi highlighter for this language |
|---|
| 672 | if (isset($language) && |
|---|
| 673 | isset($wakka->config['geshi_path']) && |
|---|
| 674 | file_exists($geshi_hi_path.DIRECTORY_SEPARATOR.$language.'.php')) |
|---|
| 675 | { |
|---|
| 676 | // check if specified filename is valid and generate code block header |
|---|
| 677 | if (isset($filename) && |
|---|
| 678 | strlen($filename) > 0 && |
|---|
| 679 | strlen($invalid) == 0) # #34 TODO: use central regex library for filename validation |
|---|
| 680 | { |
|---|
| 681 | $valid_filename = $filename; |
|---|
| 682 | // create code block header |
|---|
| 683 | $output .= '<div class="code_header">'; |
|---|
| 684 | // display filename and start line, if specified |
|---|
| 685 | $output .= $filename; |
|---|
| 686 | if (strlen($start)>0) |
|---|
| 687 | { |
|---|
| 688 | $output .= ' (line '.$start.')'; |
|---|
| 689 | } |
|---|
| 690 | $output .= '</div>'."\n"; |
|---|
| 691 | } |
|---|
| 692 | // use GeSHi for highlighting |
|---|
| 693 | $output .= $wakka->GeSHi_Highlight($code, $language, $start); |
|---|
| 694 | } |
|---|
| 695 | // check Wikka highlighter path is set and if we have an internal Wikka highlighter |
|---|
| 696 | elseif (isset($language) && |
|---|
| 697 | isset($wakka->config['wikka_formatter_path']) && |
|---|
| 698 | file_exists($wikka_hi_path.DIRECTORY_SEPARATOR.$language.'.php') && |
|---|
| 699 | 'wakka' != $language) |
|---|
| 700 | { |
|---|
| 701 | // use internal Wikka highlighter |
|---|
| 702 | $output = '<div class="code">'."\n"; |
|---|
| 703 | $output .= $wakka->Format($code, $language); |
|---|
| 704 | $output .= "</div>\n"; |
|---|
| 705 | } |
|---|
| 706 | // no language defined or no formatter found: make default code block; |
|---|
| 707 | // IncludeBuffered() will complain if 'code' formatter doesn't exist! |
|---|
| 708 | else |
|---|
| 709 | { |
|---|
| 710 | $output = '<div class="code">'."\n"; |
|---|
| 711 | $output .= $wakka->Format($code, 'code'); |
|---|
| 712 | $output .= "</div>\n"; |
|---|
| 713 | } |
|---|
| 714 | |
|---|
| 715 | // display grab button if option is set in the config file |
|---|
| 716 | if ($wakka->GetConfigValue('grabcode_button') == '1') // @@@ cast to boolean and compare to TRUE |
|---|
| 717 | { |
|---|
| 718 | $output .= $wakka->FormOpen("grabcode"); |
|---|
| 719 | // build form |
|---|
| 720 | $output .= '<input type="submit" class="grabcode" name="save" value="'.GRABCODE_BUTTON.'" title="'.rtrim(sprintf(GRABCODE_BUTTON_TITLE, $valid_filename)).'" />'; |
|---|
| 721 | $output .= '<input type="hidden" name="filename" value="'.urlencode($valid_filename).'" />'; |
|---|
| 722 | $output .= '<input type="hidden" name="code" value="'.urlencode($code).'" />'; |
|---|
| 723 | $output .= $wakka->FormClose(); |
|---|
| 724 | } |
|---|
| 725 | // output |
|---|
| 726 | return $output; |
|---|
| 727 | } |
|---|
| 728 | // forced links |
|---|
| 729 | // \S : any character that is not a whitespace character |
|---|
| 730 | // \s : any whitespace character |
|---|
| 731 | // @@@ regex accepts NO non-whitespace before whitespace, surely not correct? [[ something]] |
|---|
| 732 | else if (preg_match("/^\[\[(\S*)(\s+(.+))?\]\]$/s", $thing, $matches)) # recognize forced links across lines |
|---|
| 733 | { |
|---|
| 734 | if (!isset($matches[1])) $matches[1] = ''; #38 |
|---|
| 735 | if (!isset($matches[3])) $matches[3] = ''; #38 |
|---|
| 736 | list (, $url, , $text) = $matches; |
|---|
| 737 | if ($url) |
|---|
| 738 | { |
|---|
| 739 | //if ($url!=($url=(preg_replace("/@@|££||\[\[/","",$url))))$result="</span>"; |
|---|
| 740 | if (!$text) $text = $url; |
|---|
| 741 | //$text=preg_replace("/@@|££|\[\[/","",$text); |
|---|
| 742 | return $result.$wakka->Link($url, "", $text); |
|---|
| 743 | } |
|---|
| 744 | else |
|---|
| 745 | { |
|---|
| 746 | return ""; |
|---|
| 747 | } |
|---|
| 748 | } |
|---|
| 749 | // indented text |
|---|
| 750 | elseif (preg_match("/(^|\n)([\t~]+)(-|&|([0-9a-zA-Z]+)\))?(\n|$)/s", $thing, $matches)) |
|---|
| 751 | { |
|---|
| 752 | // new line |
|---|
| 753 | $result .= ($br ? "<br />\n" : "\n"); |
|---|
| 754 | |
|---|
| 755 | // we definitely want no line break in this one. |
|---|
| 756 | $br = 0; |
|---|
| 757 | |
|---|
| 758 | // find out which indent type we want |
|---|
| 759 | $newIndentType = $matches[3]; |
|---|
| 760 | |
|---|
| 761 | if (!$newIndentType) |
|---|
| 762 | { |
|---|
| 763 | $opener = "<div class=\"indent\">"; |
|---|
| 764 | $closer = "</div>"; $br = 1; |
|---|
| 765 | } |
|---|
| 766 | elseif ($newIndentType == "-") |
|---|
| 767 | { |
|---|
| 768 | $opener = "<ul><li>"; |
|---|
| 769 | $closer = "</li></ul>"; |
|---|
| 770 | $li = 1; |
|---|
| 771 | } |
|---|
| 772 | elseif ($newIndentType == "&") |
|---|
| 773 | { |
|---|
| 774 | $opener = "<ul class=\"thread\"><li>"; |
|---|
| 775 | $closer = "</li></ul>"; |
|---|
| 776 | $li = 1; |
|---|
| 777 | } #inline comments |
|---|
| 778 | else |
|---|
| 779 | { |
|---|
| 780 | if (ereg('[0-9]', $newIndentType[0])) { $newIndentType = '1'; } |
|---|
| 781 | elseif (ereg('[IVX]', $newIndentType[0])) { $newIndentType = 'I'; } |
|---|
| 782 | elseif (ereg('[ivx]', $newIndentType[0])) { $newIndentType = 'i'; } |
|---|
| 783 | elseif (ereg('[A-Z]', $newIndentType[0])) { $newIndentType = 'A'; } |
|---|
| 784 | elseif (ereg('[a-z]', $newIndentType[0])) { $newIndentType = 'a'; } |
|---|
| 785 | |
|---|
| 786 | $opener = '<ol type="'.$newIndentType.'"><li>'; |
|---|
| 787 | $closer = '</li></ol>'; |
|---|
| 788 | $li = 1; |
|---|
| 789 | } |
|---|
| 790 | |
|---|
| 791 | // get new indent level |
|---|
| 792 | $newIndentLevel = strlen($matches[2]); |
|---|
| 793 | if (($newIndentType != $curIndentType) && ($oldIndentLevel > 0)) |
|---|
| 794 | { |
|---|
| 795 | for (; $oldIndentLevel > 0; $oldIndentLevel--) |
|---|
| 796 | { |
|---|
| 797 | $result .= array_pop($indentClosers); |
|---|
| 798 | } |
|---|
| 799 | } |
|---|
| 800 | if ($newIndentLevel > $oldIndentLevel) |
|---|
| 801 | { |
|---|
| 802 | for ($i = 0; $i < $newIndentLevel - $oldIndentLevel; $i++) |
|---|
| 803 | { |
|---|
| 804 | $result .= $opener; |
|---|
| 805 | array_push($indentClosers, $closer); |
|---|
| 806 | } |
|---|
| 807 | } |
|---|
| 808 | elseif ($newIndentLevel < $oldIndentLevel) |
|---|
| 809 | { |
|---|
| 810 | for ($i = 0; $i < $oldIndentLevel - $newIndentLevel; $i++) |
|---|
| 811 | { |
|---|
| 812 | $result .= array_pop($indentClosers); |
|---|
| 813 | } |
|---|
| 814 | } |
|---|
| 815 | |
|---|
| 816 | $oldIndentLevel = $newIndentLevel; |
|---|
| 817 | |
|---|
| 818 | if (isset($li) && !preg_match("/".str_replace(")", "\)", $opener)."$/", $result)) |
|---|
| 819 | { |
|---|
| 820 | $result .= "</li><li>"; |
|---|
| 821 | } |
|---|
| 822 | |
|---|
| 823 | $curIndentType = $newIndentType; |
|---|
| 824 | return $result; |
|---|
| 825 | } |
|---|
| 826 | // new lines |
|---|
| 827 | else if ($thing == "\n") |
|---|
| 828 | { |
|---|
| 829 | // if we got here, there was no tab in the next line; this means that we can close all open indents. |
|---|
| 830 | $c = count($indentClosers); |
|---|
| 831 | for ($i = 0; $i < $c; $i++) |
|---|
| 832 | { |
|---|
| 833 | $result .= array_pop($indentClosers); |
|---|
| 834 | $br = 0; |
|---|
| 835 | } |
|---|
| 836 | $oldIndentLevel = 0; |
|---|
| 837 | $oldIndentLength= 0; |
|---|
| 838 | $newIndentSpace=array(); |
|---|
| 839 | |
|---|
| 840 | $result .= ($br ? "<br />\n" : "\n"); |
|---|
| 841 | $br = 1; |
|---|
| 842 | return $result; |
|---|
| 843 | } |
|---|
| 844 | // Actions |
|---|
| 845 | elseif (preg_match("/^\{\{(.*?)\}\}$/s", $thing, $matches)) |
|---|
| 846 | { |
|---|
| 847 | if ($matches[1]) |
|---|
| 848 | { |
|---|
| 849 | return $wakka->Action($matches[1]); |
|---|
| 850 | } |
|---|
| 851 | else |
|---|
| 852 | { |
|---|
| 853 | return "{{}}"; |
|---|
| 854 | } |
|---|
| 855 | } |
|---|
| 856 | // interwiki links! |
|---|
| 857 | elseif (preg_match("/^[A-ZÄÖÜ][A-Za-zÄÖÜßäöü]+[:]\S*$/s", $thing)) |
|---|
| 858 | { |
|---|
| 859 | return $wakka->Link($thing); |
|---|
| 860 | } |
|---|
| 861 | // wiki links! |
|---|
| 862 | elseif (preg_match("/^[A-ZÄÖÜ]+[a-zßäöü]+[A-Z0-9ÄÖÜ][A-Za-z0-9ÄÖÜßäöü]*$/s", $thing)) |
|---|
| 863 | { |
|---|
| 864 | return $wakka->Link($thing); |
|---|
| 865 | } |
|---|
| 866 | // separators |
|---|
| 867 | elseif (preg_match("/-{4,}/", $thing, $matches)) |
|---|
| 868 | { |
|---|
| 869 | // TODO: This could probably be improved for situations where someone puts text on the same line as a separator. |
|---|
| 870 | // Which is a stupid thing to do anyway! HAW HAW! Ahem. |
|---|
| 871 | $br = 0; |
|---|
| 872 | return "<hr />\n"; |
|---|
| 873 | } |
|---|
| 874 | // mind map xml |
|---|
| 875 | elseif (preg_match("/^<map.*<\/map>$/s", $thing)) |
|---|
| 876 | { |
|---|
| 877 | return $wakka->Action("mindmap ".$wakka->Href()."/mindmap.mm"); |
|---|
| 878 | } |
|---|
| 879 | elseif ($thing[0] == '&') |
|---|
| 880 | { |
|---|
| 881 | return ($wakka->htmlspecialchars_ent($thing)); |
|---|
| 882 | } |
|---|
| 883 | // if we reach this point, it must have been an accident. |
|---|
| 884 | return $thing; |
|---|
| 885 | } |
|---|
| 886 | } |
|---|
| 887 | |
|---|
| 888 | if (!function_exists('parse_attributes')) |
|---|
| 889 | { |
|---|
| 890 | function parse_attributes($attribs, $hints) { |
|---|
| 891 | |
|---|
| 892 | //Sort different attributes / keys to use for different elements. |
|---|
| 893 | static $attributes = array( |
|---|
| 894 | 'core' => array( 'c' => 'class','i' => 'id','s' => 'style','t' => 'title'), |
|---|
| 895 | 'i18n' => array( 'd' => 'dir','l' => 'xml:lang'), |
|---|
| 896 | 'cell' => array( 'a' => 'abbr','h' => 'headers','o' => 'scope','x' => 'colspan','y' => 'rowspan','z' => 'axis'), |
|---|
| 897 | 'other_table' => array( 'p' => 'span','u' => 'summary') |
|---|
| 898 | ); |
|---|
| 899 | |
|---|
| 900 | //adds in default hints ( core + i18n ) |
|---|
| 901 | $hints['core'] = 'core'; |
|---|
| 902 | $hints['i18n'] = 'i18n'; |
|---|
| 903 | |
|---|
| 904 | $attribs = preg_split('/;(?=.:)/', $attribs); |
|---|
| 905 | $return_value = ''; |
|---|
| 906 | |
|---|
| 907 | foreach ( $attribs as $attrib ) |
|---|
| 908 | { |
|---|
| 909 | list ($key, $value) = explode(':', $attrib, 2); |
|---|
| 910 | foreach ( $hints as $hint ) |
|---|
| 911 | { |
|---|
| 912 | $temp = $attributes[$hint]; |
|---|
| 913 | if ($temp) $a = $temp[$key]; |
|---|
| 914 | if ($a) break; |
|---|
| 915 | } |
|---|
| 916 | |
|---|
| 917 | if (!$a) |
|---|
| 918 | { |
|---|
| 919 | //This attribute isn't allowed here / is wrong. |
|---|
| 920 | // WARNING: JS vulnerability: two minus signs are not allowed in a comment, so we replace any occurence of them by underscore. |
|---|
| 921 | // Consider the code ||(p--><font size=1px><a href=...<!--:blabla |
|---|
| 922 | // When migrating to UTF-8, we could use str_replace('--', 'ââ', $key) to make things more pretty. //TODO garbled ... mdash? |
|---|
| 923 | echo '<!--Cannot find attribute for key "'.str_replace('--', '__', $key).'" from hints given.-->'."\n"; #i18n |
|---|
| 924 | } |
|---|
| 925 | else |
|---|
| 926 | { |
|---|
| 927 | // WARNING: JS vulnerability: use htmlspecialchars_ent to prevent JS attack! |
|---|
| 928 | $return_value .= ' '.$a.'="'.$GLOBALS['wakka']->htmlspecialchars_ent($value).'"'; |
|---|
| 929 | } |
|---|
| 930 | } |
|---|
| 931 | |
|---|
| 932 | return $return_value; |
|---|
| 933 | } |
|---|
| 934 | } |
|---|
| 935 | |
|---|
| 936 | $text = str_replace("\r\n", "\n", $text); |
|---|
| 937 | |
|---|
| 938 | // replace 4 consecutive spaces at the beginning of a line with tab character |
|---|
| 939 | // $text = preg_replace("/\n[ ]{4}/", "\n\t", $text); // moved to edit.php |
|---|
| 940 | |
|---|
| 941 | if ($this->handler == "show") $mind_map_pattern = "<map.*?<\/map>|"; else $mind_map_pattern = ""; |
|---|
| 942 | |
|---|
| 943 | $text = preg_replace_callback( |
|---|
| 944 | "/". |
|---|
| 945 | "%%.*?%%|". # code |
|---|
| 946 | "\"\".*?\"\"|". # literal |
|---|
| 947 | $mind_map_pattern. |
|---|
| 948 | "\[\[[^\[]*?\]\]|". # forced link |
|---|
| 949 | "-{3,}|". # forced linebreak and hr |
|---|
| 950 | "\b[a-z]+:\/\/\S+|". # URL |
|---|
| 951 | "\*\*|\'\'|\#\#|\#\%|@@|::c::|\>\>|\<\<|££|¥¥|\+\+|__|<|>|\/\/|". # Wiki markup |
|---|
| 952 | "======|=====|====|===|==|". # headings |
|---|
| 953 | "(^|\n)[\t~]+(-(?!-)|&|([0-9]+|[a-zA-Z]+)\))?|". # indents and lists |
|---|
| 954 | "\|(?:[^\|])?\|(?:\(.*?\))?(?:\{[^\{\}]*?\})?(?:\n)?|". # Simple Tables |
|---|
| 955 | "\{\{.*?\}\}|". # action |
|---|
| 956 | "\b[A-ZÄÖÜ][A-Za-zÄÖÜßäöü]+[:](?![=_])\S*\b|". # InterWiki link |
|---|
| 957 | "\b([A-ZÄÖÜ]+[a-zßäöü]+[A-Z0-9ÄÖÜ][A-Za-z0-9ÄÖÜßäöü]*)\b|". # CamelWords |
|---|
| 958 | '\\&([#a-zA-Z0-9]+;)?|'. #ampersands! Track single ampersands or any htmlentity-like (&...;) |
|---|
| 959 | "\n". # new line |
|---|
| 960 | "/ms", "wakka2callback", $text."\n"); #append \n (#444) |
|---|
| 961 | |
|---|
| 962 | // we're cutting the last <br /> |
|---|
| 963 | $text = preg_replace("/<br \/>$/","", $text); |
|---|
| 964 | |
|---|
| 965 | // @@@ don't report generation time unless some "debug mode" is on |
|---|
| 966 | if (isset($format_option) && preg_match(PATTERN_MATCH_PAGE_FORMATOPTION, $format_option)) |
|---|
| 967 | { |
|---|
| 968 | $text .= wakka2callback('closetags'); // attempt close open tags @@@ may be needed for more than whole page! |
|---|
| 969 | $idstart = getmicrotime(TRUE); |
|---|
| 970 | $text = preg_replace_callback( |
|---|
| 971 | '#('. |
|---|
| 972 | '<h[1-6].*?>.*?</h[1-6]>'. |
|---|
| 973 | // other elements to be treated go here |
|---|
| 974 | ')#ms','wakka3callback', $text); |
|---|
| 975 | printf('<!-- Header ID generation took %.6f seconds -->', (getmicrotime(TRUE) - $idstart)); #i18n |
|---|
| 976 | } |
|---|
| 977 | echo $text; |
|---|
| 978 | ?> |
|---|