1<?php $T2TVersion = "20140518"; 2/** 3 txt2tags.class.php 4 Written by (c) Petko Yotov 2012 www.pmwiki.org/Petko 5 Development sponsored by Eric Forgeot. 6 7 txt2tags is a lightweight markup language created by 8 Aurelio Jargas and written in Python (www.txt2tags.org). 9 10 This class here attempts to transpose some of the features 11 of the Python convertor to the PHP language. 12 13 Most of this script was written by Petko Yotov except: 14 - functions PSS(), Keep(), Restore(), RegExp $UEX based on 15 the PmWiki engine by Patrick R. Michaud www.pmichaud.com 16 - the RegExp $imgrx based on txt2tags.py by Aurelio Jargas 17 18 This text is Free software; you can redistribute it and/or 19 modify it under the terms of the GNU General Public License 20 as published by the Free Software Foundation; either version 3 21 of the License, or (at your option) any later version. 22 See http://www.gnu.org/copyleft/gpl.html for full details 23 and lack of warranty. 24 25 26 == How to use this script == 27 28 This class should be included from other scripts, for example: 29 30 # load the class 31 require_once('txt2tags.class.php'); 32 33 ## initialize a new T2T object: two ways 34 35 # either with an existing disk file: 36 $x = new T2T("test.t2t", true); 37 38 # or with a variable containing the entire T2T markup: 39 $x = new T2T($page["text"]); 40 41 # optional: some settings, after new/init, before go() 42 $x->enabletoc = 1; 43 $x->enableinclude = 1; 44 $x->snippets['**'] = "<strong>%s</strong>"; # instead of <b> 45 46 # run all processing 47 $x->go(); 48 49 # get the complete HTML output including <head> 50 $html = $x->fullhtml; 51 52 # alternatively, get some other variables: 53 $body = $x->bodyhtml; # only the part inside the <body> tags 54 $fullconfig = $x->config; # what was in the "config" area of the file? 55 56 57 == Notes == 58 59 - This is an early public release, ready to be tested. 60 - Including disk files is disabled out of the box, because of 61 potential vulnerabilities. Enabling it on systems where 62 untrusted people could edit the t2t content is not recommended. 63 A future version may allow the inclusion of files from the 64 current directory or only with *.t2t extension. 65 To enable inclusions, use $x->enableinclude = 1; 66 67*/ 68 69class T2T { 70 # these variables could be read or forced 71 var $title = ''; # the document title 72 var $content = ''; # the content of the t2t file 73 var $headers = ''; # the first 3 lines of the t2t file 74 var $enableheaders = 0; # enables the first 3 lines headers (default=1) 75 var $enableproc = 1; # enables the pre and post processor (default=1) 76 var $enabletagged = 1; # enables the tagged mark (''tagged'') (default=1) 77 var $enableraw = 1; # enables the raw mark (""raw"") (default=1) 78 var $enableverbatim = 1; # enables the verbatim mark (``raw``) (default=1) 79 var $enablehotlinks = 1; # enables hotlinks [http://www.externalserver.com/images.jpg] (default=1) (note: it's not enabled in the python implementation of txt2tags) 80 var $config = ''; # the full config area, including ext.ref. 81 var $bodytext = ''; # the full body text after inclusions 82 var $bodyhtml = ''; # the innerHTML of the body of the output, no <html>...<head> 83 var $fullhtml = ''; # the full <html>...</html> output 84 var $enabletoc = 0; # automatically enabled if %%toc or %!options: --toc 85 var $enableinclude = 1; # allow file inclusions 86 var $maxtoclevels = 5; # h1-h? titles go into toc, same as %!options: --toc-level 1 87 var $mtime; # last modified timestamp of the input file 88 var $date; # timestamp of the current date 89 var $cssfile = ''; # the css file to be included in the HTML header 90 var $maskemail = 0; # rewrite plaintext e-mail links 91 var $encoding = "UTF-8"; # assume default encoding if none in file 92 var $parsetargets = "html|xhtml"; # accept %!command(html) and %!command(xhtml) 93 var $snippets = array( 94 'header1' => "<h1>%s</h1>\n", # text (first line of file) 95 'header2' => "<h2>%s</h2>\n", # text 96 'header3' => "<h3>%s</h3>\n", # text 97 'headerwrap' => "<div style='text-align:center;'>\n%s</div>\n", # headers 98 'title' => '<h%d id="%s">%s</h%1$d>', # level, id, text 99 'hrule' => '<hr class="%s"/>', # light|heavy 100 'verbatim' => "<pre>\n%s</pre>", # content 101 'mono' => '<code>%s</code>', # content 102 'center' => "<center>%s</center>", # content 103 'img' => '<img align="%s" src="%s" border="0" alt=""/>', # align, url 104 'link' => '<a href="%s">%s</a>', # url, text 105 'cssfile' => '<link rel="stylesheet" href="%s" type="text/css"/>', 106 107 '**' => '<b>%s</b>', # bold content 108 '//' => '<i>%s</i>', # italics content 109 '__' => '<u>%s</u>', # underlined content 110 '--' => '<s>%s</s>', # striked content 111 112 'tableopen' => "<table %s cellpadding=\"4\">\n", # align, border 113 'tableclose' => "</table>\n", 114 'tablerow' => " <tr>\n%s </tr>\n", # tagged cells 115 'tablehead' => " <th%s>%s</th>\n", # align, content 116 'tablecell' => " <td%s>%s</td>\n", # align, content 117 118 # special 119 'blockquoteopen' => '<blockquote>', 120 'blockquoteclose' => '</blockquote>', 121 'paraopen' => '<p>', 122 'paraclose' => '</p>', 123 124 '+listopen' => "<ol>\n", 125 '+listclose' => "</ol>\n", 126 '+itemopen' => "<li>", 127 '+itemmiddle' => "", 128 '+itemclose' => "</li>\n", 129 130 '-listopen' => "<ul>\n", 131 '-listclose' => "</ul>\n", 132 '-itemopen' => "<li>", 133 '-itemmiddle' => "", 134 '-itemclose' => "</li>\n", 135 136 ':listopen' => "<dl>\n", 137 ':listclose' => "</dl>\n", 138 ':itemopen' => "<dt>", 139 ':itemmiddle' => "</dt><dd>\n", 140 ':itemclose' => "</dd>\n", 141 'listindent' => ' ', # nicer HTML output 142 143 # title, encoding, version, styles, body 144 'html' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 145<html xmlns="http://www.w3.org/1999/xhtml"> 146<head> 147<title>%s</title> 148<meta http-equiv="Content-Type" content="text/html; charset=%s"/> 149<meta name="generator" content="txt2tags.class.php version %s"/> 150%s 151</head> 152<body bgcolor="white" text="black"> 153%s 154</body></html>'); 155 # used internally 156 var $infile = array(); 157 var $outfile = array(); 158 var $preproc = array(); 159 var $postproc = array(); 160 var $KPV = array(); 161 var $KPCount = 0; 162 var $csslink = ''; 163 function T2T($input, $isfile = 0) { 164 165 $this->set_macros($input, $isfile); # macros 166 # get content 167 $this->content = $isfile 168 ? $this->read($input, true) 169 : str_replace("\r", '', $input); 170 171 # get header, config, body 172 $this->R = $this->head_conf_body($this->content); 173 174 # public variables 175 $this->headers = implode("\n", $this->R['header']); 176 $this->config = implode("\n", $this->R['config']); 177 $this->bodytext = implode("\n", $this->R['body']); 178 } 179 180 function go($output = '') { # run the full processing 181 182 $this->parse_config($this->R['config']); # read config settings 183 $lines = $this->run_preproc($this->R['body']); # run %!preproc replacements 184 185 # strip comments, identify areas and titles, tables and horizontal rules (bars) 186 $lines = $this->firstpass($lines); 187 188 # second pass 189 $lines = $this->secondpass($this->R['header'], $lines); 190 191 # restore 192 $body = implode("\n", $lines); 193 $body = $this->Restore($body); 194 $body = str_replace(array("\032\032", "\033\033"), '', $body); 195 196 # public variables 197 $this->title = $this->esc($this->run_macros($this->R['header'][0])); 198 $this->bodyhtml = $this->run_postproc($body); # %!postproc replacements 199 $html = sprintf($this->snippets['html'], $this->title, $this->encoding, 200 $GLOBALS['T2TVersion'], $this->csslink, $body); 201 $this->fullhtml = $this->run_postproc($html); # %!postproc replacements 202 203 if($output=='body') return $this->bodyhtml; 204 if($output=='html') return $this->fullhtml; 205 return; 206 } 207 208 function parse_config($lines) { # try to read the supported configuration options 209 $opts = "encoding|style|postproc|preproc|options"; 210 $tgts = $this->parsetargets; 211 foreach($lines as $c){ 212 if(preg_match("/^%!\\s*({$opts})(?:\\((?:{$tgts})\\))?\\s*:\\s*(.*)$/i", $c, $m)) { 213 # options, preproc and postproc are cumulative 214 list(, $setting, $val) = $m; 215 switch(strtolower($setting)) { 216 case "encoding" : $this->encoding = trim($val); break; 217 case "style" : $this->csslink = sprintf($this->snippets['cssfile'], trim($val)); 218 case "preproc" : 219 if ($this->enableproc == 1) { 220 $this->preproc[] = $this->add_proc($setting, trim($val)); 221 break; 222 } 223 case "postproc" : 224 if ($this->enableproc == 1) { 225 $this->postproc[] = $this->add_proc($setting, trim($val)); 226 break; 227 } 228 case "options": 229 if(strpos(" $val", '--mask-email')) $this->maskemail = 1; 230 if(preg_match('/--toc(?!-)/', $val)) $this->enabletoc = 1; 231 if(preg_match('/--toc-level[= ]+([1-5])/', $val, $n)) $this->maxtoclevels = $n[1]; 232 if(preg_match('/--encoding[= ]+(\\S+)/', $val, $n)) $this->encoding = $n[1]; 233 if(preg_match('/--style[= ]+(\\S+)/', $val, $n)) 234 $this->csslink = sprintf($this->snippets['cssfile'], trim($val)); 235 break; 236 } 237 } 238 } 239 } 240 241 function add_proc($when, $x) { # add a pre-processor or a postprocessor 242 if(! preg_match("/^ *(\"[^\"]+\"|'[^']+'|\\S+)\\s*(.*)$/", $x, $m)) return; 243 244 $s = preg_replace('/^("|\')(.*?)\\1$/', '$2', trim($m[1])); 245 $r = preg_replace('/^("|\')(.*?)\\1$/', '$2', trim(@$m[2])); 246 $r = preg_replace('/\\\\(?=[0-9])/', '$', $r); 247 $r = str_replace(array('\\n', '\\t'), array("\n", "\t"), $r); 248 return array("\032$s\032m", $r); 249 250 } 251 function run_preproc($lines) { # make the replacements 252 $body = implode("\n", $lines); 253 foreach($this->preproc as $a) { 254 $body = preg_replace($a[0], $a[1], $body); 255 } 256 return explode("\n", $body); 257 } 258 function run_postproc($body) { # make the replacements 259 foreach($this->postproc as $a) { 260 $body = preg_replace($a[0], $a[1], $body); 261 } 262 return $body; # end of processing 263 } 264 265 function firstpass($lines) { # strip comments, identify areas, titles, toc 266 $snippets = $this->snippets; 267 $toc = array(); 268 $postoc = 0; 269 270 $lines2 = array(); 271 $openarea = ''; 272 $areacontent = ''; 273 $toccnt = 0; 274 $tocnbs = array(0, 0, 0, 0, 0, 0); 275 $table = ''; 276 foreach($lines as $line) { 277 278 # special areas raw, tagged, verbatim, comments 279 if($openarea) { 280 if($line == $openarea) { # close area 281 if(rtrim($line) != "%%%") # comment areas 282 $lines2[] = $this->closeRTV($openarea, $areacontent); 283 } 284 else { # fill area 285 $areacontent .= "$line\n"; 286 } 287 continue; 288 } 289 if(preg_match('/^("""|```|\'\'\'|%%%) *$/', $line)) { # open area 290 $openarea = trim($line); 291 continue; 292 } 293 294 if($line!='' && $line{0}=='%' && ! preg_match('/^%%(infile|outfile|date|mtime|rand|toc\\s*$)/i', $line) ) 295 continue; # remove comment lines 296 297 # special lines raw, tagged, verbatim 298 if(preg_match('/^("""|```|\'\'\') /', $line, $m)) { 299 $lines2[] = $this->closeRTV($m[1], substr($line, 4)); 300 continue; 301 } 302 303 # Title, Numbered Title 304 if(preg_match('/^ *((=){1,5})(?!=)\\s*(\\S.*[^=]|[^\\s=])\\1(?:\\[([\\w-]+)\\])?\\s*$/', $line, $m) || 305 preg_match('/^ *((\\+){1,5})(?!\\+)\\s*(\\S.*[^+]|[^\\s=])\\1(?:\\[([\\w-]+)\\])?\\s*$/', $line, $m) ) { 306 $toccnt++; 307 $anchor = @$m[4] ? $m[4] : "toc$toccnt"; 308 $txt = $this->esc(trim($m[3])); 309 $level = strlen($m[1]); 310 311 if($m[2]=='+') { 312 if($level>$tocnbs[0])$tocnbs[$level] = 1; 313 else $tocnbs[$level]++; 314 if($level<$tocnbs[0])for($i=$level+1; $i<6; $i++) $tocnbs[$i] = 0; 315 316 $prefix = implode(".", array_slice($tocnbs, 1, $level)); 317 $prefix = preg_replace('/^(0\\.)+/', '', $prefix); 318 $txt = "$prefix. $txt"; 319 $tocnbs[0] = $level; 320 } 321 322 $txt = $this->Keep($txt); 323 $lines2[] = "\032\032".sprintf($snippets['title'], $level, $anchor, $txt); # \032: block that cannot be nested in lists 324 325 # collect toc entries 326 if($this->maxtoclevels>=$level) 327 $toc[] = $this->sp($level) . "- [$txt #$anchor]"; 328 329 continue; 330 } 331 # tables 332 if(preg_match('/^ *(\\|\\|?) /', $line, $m)) { 333 if(!$table) { # open table 334 $attr = ($line{0}==' ')? ' align="center"' : ""; 335 if(preg_match('/\\|\\s*$/', $line)) { 336 $attr .= ' border="1"'; 337 } 338 339 $table = sprintf($snippets['tableopen'], $attr); 340 } 341 # fill table 342 if($m[1]=='||') $fmt = $snippets['tablehead']; 343 else $fmt = $snippets['tablecell']; 344 345 $line = $this->run_inline($line); 346 347 $row = substr($line, strlen($m[0])); 348 349 if(! preg_match('/\\|\\s*$/', $row)) $row .= ' | '; 350 $m = preg_split('/( \\|+(?: |$))/', $row, -1, PREG_SPLIT_DELIM_CAPTURE); 351 352 $cells = ''; 353 for($i=1; $i<count($m); $i+=2){ 354 $c = $m[$i-1]; 355 $attr = ''; 356 if($c && $c{0}==' ') { 357 $attr = (substr($c, -1)==' ') ? ' align="center"' : ' align="right"'; 358 } 359 $span = strlen(trim($m[$i])); 360 if($span>1) $attr .= " colspan=\"$span\""; 361 $cells .= sprintf($fmt, $attr, $c); 362 } 363 $table .= sprintf($snippets['tablerow'], $cells); 364 continue; 365 } 366 elseif($table) { # close table 367 $lines2[] = "\033\033". $this->Keep($table . $snippets['tableclose']); # \033: block that can be nested in lists 368 $table = ''; 369 } 370 # horizontal rule (bar1, bar2) 371 if(preg_match('/^ *(=|-|_){20,}\\s*$/', $line, $m)) { 372 $class = $m[1] == "=" ? 'heavy':'light'; 373 $lines2[] = "\032\032".$this->Keep(sprintf($snippets['hrule'], $class)); 374 continue; 375 } 376 if(preg_match("/^ +\\[([\034\\w_,.+%$#@!?+~\\/-]+\\.(?:png|jpe?g|gif|bmp|svg))\\] +$/i", $line)) { 377 $lines2[] = "\033\033". $this->Keep(sprintf($snippets['center'], $this->run_inline($line))); 378 continue; 379 } 380 381 if(trim(strtolower($line))=='%%toc') { 382 $this->enabletoc = $postoc = 1; 383 $line = '%%toc'; 384 } 385 $lines2[] = $line; 386 } # end foreach line 387 388 # close ALL 389 if($openarea && $openarea != '%%%') # close all areas 390 $lines2[] = $this->closeRTV($openarea, $areacontent); 391 392 if($table) 393 $lines2[] = "\033\033". $this->Keep($table . $snippets['tableclose']."\n"); 394 395 if($this->enabletoc && count($toc)) { 396 if($postoc) { # there is %%toc in the page 397 array_unshift($toc, "\032\032"); 398 $toc[] = "\032\032"; 399 foreach($lines2 as $k=>$v) { 400 if($v=='%%toc') array_splice($lines2, $k, 1, $toc); 401 } 402 } 403 else { # before the body: bar, toc-list, bar 404 $bar = "\032\032".$this->Keep(sprintf($snippets['hrule'], 'light')); 405 array_unshift($toc, $bar); 406 $toc[] = $bar; 407 408 $lines2 = array_merge($toc, $lines2); 409 } 410 } 411 return $lines2; 412 } 413 414 function secondpass($headers, $lines) { # all other marks 415 $snippets = $this->snippets; 416 417 $lines2 = array(); 418 $html = ''; 419 for($i=0; $i<3; $i++) { 420 $j = $i+1; 421 $h = $this->esc($headers[$i]); 422 if($h) $html .= sprintf($snippets["header$j"], $this->run_macros($h)); 423 } 424 if($html) $lines2[] = $this->Keep(sprintf($snippets["headerwrap"], $html)); 425 426 $blockquote = 0; 427 $blockquotecontent = ''; 428 $para = false; 429 $openlist = 0; 430 $listcontent = ''; 431 $ListLevels = array(array(-1, '', '')); # $level, $spaces, $type 432 433 foreach($lines as $line) { 434 435 # blockquote 436 if(preg_match('/^(\\t+)([^\\t].*)$/', $line, $m)) { 437 if($para) { 438 $para = false; 439 $lines2[] = $snippets['paraclose']; 440 } 441 $level = strlen($m[1]); 442 $blockquotecontent .= $this->fixblockquote($blockquote, $level) 443 . $m[1]. $this->run_inline($m[2])."\n"; 444 continue; 445 } 446 elseif($blockquote) { # close bq 447 $lines2[] = "\032\032".$blockquotecontent . $this->fixblockquote($blockquote, 0); 448 $blockquotecontent = ''; 449 } 450 451 # List, Numbered List, Definition List 452 if(preg_match('/^( *)([+-]) (\\S.*)$/', $line, $m) || 453 preg_match( '/^( *)(:) ( *\\S.*)$/', $line, $m)) { 454 $openlist = 2; 455 if($para) { 456 $para = false; 457 $lines2[] = $snippets['paraclose']; 458 } 459 460 list(, $spaces, $type, $text) = $m; 461 $text = $this->run_inline($text); 462 $upped = 0; 463 464 while(count($ListLevels)>0) { 465 list($plevel, $pspaces, $ptype) = array_pop($ListLevels); 466 467 ## close previous list 468 if($plevel>=0 && strcmp($spaces, $pspaces)<0) { 469 $listcontent .= "\n".$this->sp($plevel).$snippets["{$ptype}itemclose"] . $this->sp($plevel).$snippets["{$ptype}listclose"]; 470 $upped ++; 471 continue; 472 } 473 if($upped) $pspaces = $spaces; 474 $ListLevels[] = array($plevel, $pspaces, $ptype); # restore 475 476 ## open list 477 if($plevel<0 || strcmp($spaces, $pspaces)>0) { # new list/sublist 478 $level = $plevel+1; 479 $listcontent .= "\n" . $this->sp($level). $snippets["{$type}listopen"]; 480 $ListLevels[] = array($level, $spaces, $type); 481 } 482 else { ## close prev item 483 $listcontent .= "\n".$this->sp($plevel).$snippets["{$ptype}itemclose"]; 484 $level = $plevel; 485 if($ptype!=$type) { 486 $listcontent .= "\n".$this->sp($level).$snippets["{$ptype}listclose"] 487 ."\n".$this->sp($level).$snippets["{$type}listopen"]; 488 $ListLevels[count($ListLevels)-1][2] = $type; # restore 489 } 490 } 491 ## open item 492 $listcontent .= $this->sp($level).$snippets["{$type}itemopen"]; 493 494 ## fill content 495 $listcontent .= $text . $snippets["{$type}itemmiddle"]; 496 497 break; 498 } 499 continue; 500 } 501 elseif($openlist) { 502 if(trim($line)=='' || strpos($line, "\032\032")===0 ) { 503 $openlist --; 504 if(!$openlist || strpos($line, "\032\032")===0) { # second empty line, close ALL 505 while(count($ListLevels)>1) { 506 list($plevel, $pspaces, $ptype) = array_pop($ListLevels); 507 $listcontent .= "\n".$this->sp($plevel).$snippets["{$ptype}itemclose"] 508 .$this->sp($plevel).$snippets["{$ptype}listclose"]; 509 } 510 $lines2[] = $this->Keep($listcontent); 511 $lines2[] = $line; 512 $listcontent = "";$openlist=0; 513 continue; 514 } 515 else { 516 list($plevel, ,) = $ListLevels[count($ListLevels)-1]; 517 $listcontent .= $this->sp($plevel+1)."{$snippets["paraopen"]}{$snippets["paraclose"]}\n"; 518 } 519 } 520 else { 521 list($plevel, $pspaces, $ptype) = array_pop($ListLevels); 522 if(preg_match('/^(( *)([+-:])) *$/', $line, $m) && $m[1]== "$pspaces$ptype") { # close 523 $listcontent .= "\n".$this->sp($plevel).$snippets["{$ptype}itemclose"] 524 . $this->sp($plevel).$snippets["{$ptype}listclose"]; 525 if($plevel) $openlist = 2; 526 else { 527 $lines2[] = $this->Keep($listcontent); 528 $listcontent = "";$openlist=0; 529 continue; 530 } 531 } 532 else { 533 if($openlist==1) 534 $listcontent .= $this->sp($plevel+1)."{$snippets["paraopen"]}{$snippets["paraclose"]}\n"; 535 $openlist = 2; 536 $ListLevels[] = array($plevel, $pspaces, $ptype); 537 $listcontent .= $this->sp($plevel+1).$this->run_inline($line)."\n"; 538 } 539 } 540 continue; 541 } 542 543 if(preg_match("/^[\032\033]{2}/", $line)) { 544 if($para) { 545 $para = false; 546 $lines2[] = $snippets['paraclose']; 547 } 548 $lines2[] = $line; 549 continue; 550 } 551 552 if($para) { 553 if(trim($line)=='') { 554 $para = false; 555 $lines2[] = $snippets['paraclose']; 556 } 557 else { 558 $lines2[] = $this->run_inline($line); 559 } 560 continue; 561 } 562 # $para = false; 563 if(trim($line)!='') { 564 $lines2[] = $snippets['paraopen']; 565 $lines2[] = $this->run_inline($line); 566 $para = true; 567 continue; 568 } 569 570 $lines2[] = $line; 571 } 572 # close ALL 573 if($blockquote) 574 $lines2[] = $this->Keep($blockquotecontent . $this->fixblockquote($blockquote, 0)); 575 if($openlist) { 576 while(count($ListLevels)>1) { 577 list($plevel, $pspaces, $ptype) = array_pop($ListLevels); 578 $listcontent .= "\n".$this->sp($plevel).$snippets["{$ptype}itemclose"] 579 .$this->sp($plevel).$snippets["{$ptype}listclose"]; 580 } 581 $lines2[] = $this->Keep($listcontent); 582 } 583 if($para) { 584 $para = false; 585 $lines2[] = $snippets['paraclose']; 586 } 587 588 return $lines2; 589 } 590 591 function run_inline($line) { # inline transformations (links, images, bold, mono...) 592 $snippets = $this->snippets; 593 # inline Raw, Mono, Tagged 594 if(preg_match_all('/(\'|"|`){2}([^\\s](.*?[^\\s])?\\1*)\\1\\1/', $line, $m, PREG_SET_ORDER)) { 595 foreach($m as $a) { 596 $type = $a[1].$a[1]; 597 $c = $this->PSS($a[2]); 598 $tmp = $this->closeRTV($type, $c); 599 $line = preg_replace('/(\'|"|`){2}([^\\s](.*?[^\\s])?\\1*)\\1\\1/', $tmp, $line, 1); 600 } 601 } 602 # macros 603 $line = $this->run_macros($line); 604 605 # <[img]> 606 $imgrx = "\\[([\034\\w_,.+%$#@!?+~\\/-]+\\.(?:png|jpe?g|gif|bmp))\\]"; 607 608 $line = preg_replace("/^$imgrx(?=.)/ei", 609 "\$this->Keep(sprintf(\$snippets['img'], 'left', '$1'))", $line); 610 $line = preg_replace("/(?<=.)$imgrx$/ei", 611 "\$this->Keep(sprintf(\$snippets['img'], 'right', '$1'))", $line); 612 613 614 $line = preg_replace("/$imgrx/ei", 615 "\$this->Keep(sprintf(\$snippets['img'], 'middle', '$1', 'middle'))", $line); 616 617 $UEX = '<>"{}|\\\\^`()\\[\\]\''; # UrlExcludeChars 618 $PRT = '(?:https?|ftp|news|telnet|gopher|wais|mailto):'; 619 620 if ($this->enablehotlinks == 0) { 621 $Links = array( 622 "{$PRT}[^\\s$UEX]+" =>'', 623 "www\\d?\\.[^\\s$UEX]+" =>'http://', # lazy links 624 "ftp\\d?\\.[^\\s$UEX]+" =>'ftp://', # lazy links 625 "\\w[\\w.-]+@[\\w-.]+[^\\s$UEX]+" =>'mailto:', # lazy links 626 ); # 627 } 628 else { 629 $Links = array( 630 //"{$PRT}[^\\s$UEX]+" =>'', # allows hotlinks by disabling this part 631 //"www\\d?\\.[^\\s$UEX]+" =>'http://', # lazy links won't work here 632 "ftp\\d?\\.[^\\s$UEX]+" =>'ftp://', # lazy links 633 "\\w[\\w.-]+@[\\w-.]+[^\\s$UEX]+" =>'mailto:', # lazy links 634 ); # 635 } 636 637 # [txt link], [txt #anchor] 638 foreach($Links as $k=>$v) { 639 $line = preg_replace("/\\[([^\\]]+?) +($k)\\]/ei", 640 "\$this->Keep(sprintf(\$snippets['link'], \$this->esc('$v$2'), \$this->esc('$1', 1)))", $line); 641 } 642 # local links 643 $line = preg_replace("/\\[([^\\]]+?) +([^\\s$UEX]+)\\]/ei", 644 "\$this->Keep(sprintf(\$snippets['link'], \$this->esc('$2'), \$this->esc('$1', 1)))", $line); 645 646 # free links www.link, e@mail, http://link 647 foreach($Links as $k=>$v) { 648 if($v=='mailto:' && $this->maskemail) { 649 $line = preg_replace("/\\b({$k}[^\\s.,?!$UEX])/ei", 650 "\$this->Keep('<' . str_replace(array('@', '.'), array(' (a) ', ' '), '$1') . '>$2')", $line); 651 } 652 else { 653 $line = preg_replace("/\\b({$k}[^\\s.,?!$UEX])/ei", 654 "\$this->Keep(sprintf(\$snippets['link'], \$this->esc('$v$1'), \$this->esc('$1')))", $line); 655 } 656 } 657 658 $line = $this->esc($line); 659 660 # Bold, Italic, Underline, Strike 661 $b = array('*', '/', '_', '-'); 662 foreach($b as $c) { 663 $q = preg_quote($c, '/'); 664 $line = preg_replace("/($q){2}([^\s](?:.*?\\S)?\\1*)\\1\\1/e", 665 "sprintf(\$snippets['$c$c'], \$this->PSS('$2'))", $line); 666 } 667 return $line; 668 } 669 670 function set_macros($input, $isfile = 0) { 671 $this->date=time(); 672 if($isfile && file_exists($input)) { 673 $this->mtime = filemtime($input); 674 $this->infile = $this->fileattr($input); 675 } 676 else { 677 $this->mtime = time(); 678 $this->infile = $this->fileattr('-'); 679 } 680 $this->outfile = $this->fileattr('-'); 681 } 682 function run_macros($line) { 683 $line = preg_replace('/%%(date|mtime)(\\((.+?)\\))?/ie', 684 'strftime("$2"? $this->PSS("$3"):"%Y%m%d", $this->$1)', $line); 685 $line = preg_replace('/%%infile(?:\\((.*?)\\))?/ie', 686 '"$1" ? str_replace(array_keys($this->infile), array_values($this->infile), "$1") 687 : $this->infile["%f"]', $line); 688 $line = preg_replace('/%%outfile(?:\\((.*?)\\))?/ie', 689 '"$1" ? str_replace(array_keys($this->outfile), array_values($this->outfile), "$1") 690 : $this->outfile["%f"]', $line); 691 /*$line = preg_replace_callback('/%%rand\([0-9]+,[0-9]+\)/', 'create_function(return(rand($1,$2);))', $line); 692 $line = preg_replace('/%%rand\([0-9]+,[0-9]+\)/i', '<? rand($1,$2); ?>', $line); 693 $line = preg_replace_callback('/%%rand\\(([0-9]+),([0-9]+)\\)/', 'return(rand($1,$2);)', $line); 694*/ 695 return $line; 696 } 697 698 function fixblockquote(&$prev, $curr) { # close open blocks, open sub-blocks 699 $s = $this->snippets; 700 $x = ''; 701 while ($prev<$curr) $x .= str_repeat("\t", ++$prev) . $s['blockquoteopen'] . "\n"; 702 while ($prev>$curr) $x .= str_repeat("\t", $prev--) . $s['blockquoteclose'] . "\n"; 703 return $x; 704 } 705 706 function closeRTV(&$type, &$x) { # Raw, Tagged or Verbadim lines/areas 707 switch($type{0}) { 708 case '%': $type = $x = ''; return ''; 709 case "'": 710 if ($this->enabletagged == 1) { 711 $y = $x; 712 break; 713 } 714 715 case '"': # raw 716 if ($this->enableraw == 1) { 717 $y = $this->esc($x); 718 break; 719 } 720 case '`': # verbatim, mono 721 if ($this->enableverbatim == 1) { 722 $s = $this->snippets; 723 $fmt = (strlen($type)==2) ? $s['mono'] : $s['verbatim']; 724 $y = sprintf($fmt, $this->esc($x)); 725 break; 726 } 727 else { 728 $y = $this->esc($x); 729 } 730 } 731 $block = (strlen($type)==3) ? "\033\033" : ''; 732 $type = $x = ''; 733 return $block. $this->Keep($y); 734 } 735 function PSS($x) { return str_replace('\\"','"',$x); } # Strip RegExp slashes 736 function Keep($x) { # preserves a string from being processed by wiki markups 737 $x = $this->Restore($x); 738 $this->KPCount++; $this->KPV[$this->KPCount]=$x; 739 return "\034\034{$this->KPCount}\034\034"; 740 } 741 function Restore($x) { # recovers all hidden strings 742 return preg_replace("/\034\034(\\d.*?)\034\034/e", "\$this->KPV['\$1']", $x); 743 } 744 function sp($n){ # add spaces for nicer indented HTML source code 745 return str_repeat($this->snippets['listindent'], $n); 746 } 747 748 function esc($x, $pss=0) { # htmlspecialchars 749 if($pss) $x = $this->PSS($x); 750 return str_replace( 751 array('&', '<', '>', '$'), 752 array('&', '<', '>', '$'), 753 $x); 754 } 755 756 function fileattr($fname) { # variables that can be in %%infile() 757 if($fname == '-') return array( 758 '' => '-', '%f'=> '-', '%F'=> '-', '%e'=> '', 759 '%p'=> '-', '%d'=> '.', '%D'=> '.', '%%'=>'%'); 760 preg_match('/\\.([^.\\/]+)$/',$fname, $m); $ext=@$m[1]; 761 return array( 762 '' => basename($fname), 763 '%f'=> basename($fname), 764 '%F'=> preg_replace('/\\.[^.]+$/', '', basename($fname)), 765 '%e'=> $m[1], 766 '%p'=>realpath($fname), 767 '%d'=>dirname(realpath($fname)), 768 '%D'=>basename(dirname(realpath($fname))), 769 '%%'=>'%'); 770 } 771 772 function read($filename, $allowed = false) { # get a file content 773 if(!$allowed) return ''; 774 if(!file_exists($filename)) return ''; 775 return str_replace("\r", '', implode('', @file($filename))); 776 } 777 778 # get the Header, Config area and Body of a t2t file 779 # the function will include the content of any included files 780 function head_conf_body($content) { 781 $lines = explode("\n", $content); 782 $R = array(); 783 $R['header'] = $R['config'] = $R['body'] = array(); 784 # headers 785 if ($this->enableheaders == 0) { 786 $R['header'][0] = $R['header'][1] = $R['header'][2] = ''; 787 } 788 else if($lines[0]=='') { 789 $R['header'][0] = $R['header'][1] = $R['header'][2] = ''; 790 array_shift($lines); 791 } 792 else { 793 for ($i=0; $i<3 && isset($lines[0]); $i++) { 794 $R["header"][$i] = array_shift($lines); 795 } 796 } 797 # config 798 $mlcomment = false; 799 while(isset($lines[0])) { 800 $line = array_shift($lines); 801 if(rtrim($line) == '%%%') { # comment areas in Config 802 $mlcomment = ! $mlcomment; 803 continue; 804 } 805 if($mlcomment || trim($line)=='') continue; 806 807 if(preg_match('/^%!\\s*includeconf\\s*:\\s*(.+)$/', $line, $m)) { 808 $f = trim($m[1]); 809 if($f{0}!='/') $f = $this->infile['%d'] . DIRECTORY_SEPARATOR . $f; 810 $r = $this->head_conf_body($this->read($f, $this->enableinclude)); 811 for($i=count($r['config'])-1; $i>=0; $i--) 812 array_unshift($lines, $r['config'][$i]); 813 continue; 814 } 815 816 if($line{0} != '%' || preg_match('/^%(%(date|mtime|toc|infile|outfile|rand)|! *include)/i', $line)) { 817 array_unshift($lines, $line); 818 break; 819 } 820 $R["config"][] = $line; 821 } 822 823 # body 824 $mlcomment = false; 825 while(isset($lines[0])) { 826 $line = array_shift($lines); 827 828 if(rtrim($line) == '%%%') { # comment areas 829 $mlcomment = ! $mlcomment; 830 continue; 831 } 832 if($mlcomment) continue; 833 834 if(preg_match('/^%!\\s*include(?:\\(x?html\\))?\\s*:\\s*(``|\'\'|""|)(.+)\\1\\s*$/', $line, $m)) { 835 836 $f = trim($m[2]); 837 if($f{0}!='/') $f = $this->infile['%d'] . DIRECTORY_SEPARATOR . $f; 838 $r = $this->head_conf_body($this->read($f, $this->enableinclude)); 839 840 if($m[1]) { 841 $q = implode("\n", $r['body'])."\n"; 842 $type = str_repeat($m[1]{1}, 3); 843 $line = $this->closeRTV($type, $q); 844 } 845 else { 846 for($i=count($r['body'])-1; $i>=0; $i--) 847 array_unshift($lines, $r['body'][$i]); 848 continue; 849 } 850 } 851 $R['body'][] = $line; 852 } 853 return $R; 854 } 855} 856 857