| [ PHPXref.com ] | [ Generated: Sun Jul 20 21:04:14 2008 ] | [ WikkiTikkiTavi 0.26 ] |
| [ Index ] [ Variables ] [ Functions ] [ Classes ] [ Constants ] [ Statistics ] | ||
[Summary view] [Print] [Text view]
1 <?php 2 // $Id: transforms.php,v 1.57 2005/03/30 21:49:15 holroy Exp $ 3 4 // The main parser components. Each of these takes a line of text and scans it 5 // for particular wiki markup. It converts markup elements to 6 // $FlgChr . x . $FlgChr, where x is an index into the global array $Entity, 7 // which contains descriptions of each markup entity. Later, these will be 8 // converted back into HTML (or, in the future, perhaps some other 9 // representation such as XML). 10 11 function parse_noop($text) 12 { 13 return $text; 14 } 15 16 // The following function "corrects" for PHP's odd preg_replace behavior. 17 // Back-references have backslashes inserted before certain quotes 18 // (specifically, whichever quote was used around the backreference); this 19 // function removes remove those backslashes. 20 21 function q1($text) 22 { return str_replace('\\"', '"', $text); } 23 24 function split_curly_options($text) 25 { 26 $retArr = array(); 27 28 if (empty($text)) { 29 return $retArr; 30 } 31 $options = preg_split('|,|', $text); 32 33 foreach ($options as $opt) { 34 if (empty($opt)) { continue; } 35 if (preg_match('/(.*)=(.*)/', $opt, $match)) { 36 $retArr[$match[1]] = $match[2]; 37 } else { 38 $retArr[$opt] = ''; 39 } 40 } 41 return $retArr; 42 } 43 44 function validate_page($page) 45 { 46 global $FlgChr; 47 48 $p = parse_wikiname($page, 1); 49 if(preg_match('/^' . $FlgChr .'!?'. '\\d+' . $FlgChr . '$/', $p)) 50 { return 1; } 51 $p = parse_freelink('((' . $page . '))', 1); 52 if(preg_match('/^' . $FlgChr . '!?'. '\\d+' . $FlgChr . '$/', $p)) 53 { return 2; } 54 return 0; 55 } 56 57 function pre_parser($text) 58 { 59 // Before parsing the whole text, do check for line continuation and forced 60 // line breaks. To achieve this, and still have code-sections, code-sections 61 // need to be parsed in this section as well 62 63 // Parse the code sections, to escape them from the line control 64 $text = preg_replace("/(?:^|\n)\s*<((?:php)?code)>\s*\n(.*\n)\s*<\\/\\1>\s*(?=\n|$)/Usei", 65 "q1('\n').code_token('\\1',q1('\\2'))", $text); 66 67 // Insert page breaks to lines ending in a double \ 68 $text = preg_replace("/\\\\\\\\\n\s*/se", "new_entity(array('newline'))", 69 $text); 70 71 // Concatenate lines ending in a single \ 72 $text = preg_replace("/\\\\\n[ \t]*/s", " ", $text); 73 74 return $text; 75 } 76 77 function code_token($codetype, $code) 78 { 79 global $FlgChr, $Entity; 80 81 if (stristr("code", $codetype)) 82 { $Entity[count($Entity)] = array('code', parse_htmlisms($code)); } 83 else if (stristr("phpcode", $codetype)) 84 { $Entity[count($Entity)] = array('phpcode', $code); } 85 86 return $FlgChr . (count($Entity)-1) . $FlgChr; //Is a blockelement 87 } 88 89 function parse_elem_flag($text) 90 { 91 global $FlgChr; 92 93 // Hide element flags (0xFF) from view. 94 return preg_replace('/' . $FlgChr . '/e', "new_entity(array('raw', '$FlgChr'))", $text, -1); 95 } 96 97 function new_entity($array,$blockElem=true) 98 { 99 global $Entity, $FlgChr; 100 101 $Entity[count($Entity)] = $array; 102 return $FlgChr . ($blockElem ? '' : '!') . (count($Entity) - 1) . $FlgChr; 103 } 104 105 function parse_wikiname($text, $validate = 0) 106 { 107 global $LinkPtn, $EnableWikiLinks; 108 109 if(!$EnableWikiLinks) { return $text; } 110 111 if($validate) 112 { $ptn = "/(^|[^A-Za-z])(\\/?$LinkPtn)(())(\"\")?/e"; } 113 else 114 { $ptn = "/(^|[^A-Za-z])(!?\\/?$LinkPtn)((\#[A-Za-z]([-A-Za-z0-9_:.]*[-A-Za-z0-9_])?)?)(\"\")?/e"; } 115 116 return preg_replace($ptn, 117 "q1('\\1').wikiname_token(q1('\\2'),'\\3')", 118 $text, -1); 119 } 120 121 function wikiname_token($name, $anchor) 122 { 123 global $ParseObject; 124 if($name[0] == '!') // No-link escape sequence. 125 { return substr($name, 1); } // Trim leading '!'. 126 $link = $name; 127 // translate sub-page markup into a qualified wikiword 128 if ($name[0] == '/') 129 { 130 if (preg_match("|(.*)\\/[^\\/]*|", $ParseObject, $path)) 131 { $link = $path[1] . $name; } 132 else 133 { $link = substr($name,1); } 134 } 135 136 return new_entity(array('ref', $link, $name, '', $anchor, $anchor),false); 137 } 138 139 function parse_freelink($text, $validate = 0) 140 { 141 global $EnableFreeLinks; 142 143 if(!$EnableFreeLinks) { return $text; } 144 145 if($validate) 146 { 147 $ptn = "/\\(\\(([^\\|\\(\\)]+)()()\\)\\)/e"; 148 } 149 else 150 { 151 $ptn = "/(!?\\(\\(([^\\|\\(\\)]+)(\\|[^\\(#]+)?(\\#[A-Za-z][-A-Za-z0-9_:.]*)?()\\)\\))/e"; 152 } 153 154 return preg_replace($ptn, 155 "freelink_token(q1('\\2'), q1('\\3'), '\\4', '', '\\1')", 156 $text, -1); 157 } 158 159 function freelink_token($link, $appearance, $anchor, $anchor_appearance, $nolink) 160 { 161 global $ParseObject, $FlgChr; 162 if($nolink[0] == '!') // No-link escape sequence. 163 { return new_entity(array('raw', substr($nolink, 1))); } // Trim leading '!' 164 165 if($appearance == '') 166 { $appearance = $link; } 167 else 168 { 169 $appearance = substr($appearance, 1); // Trim leading '|'. 170 if (preg_match("/$FlgChr/", $appearance)) 171 { $appearance = parse_elements($appearance); } 172 } 173 174 // translate sub-page markup into a qualified wikiword 175 if (($link != '') and ($link[0] == '/')) 176 { 177 if (preg_match("|(.*)\\/[^\\/]*|", $ParseObject, $path)) 178 { $link = $path[1] . $link; } 179 else 180 { $link = substr($link,1); } 181 } 182 if (preg_match("/$FlgChr/", $link)) 183 { return $nolink; } 184 else 185 { 186 return new_entity(array('ref', $link, $appearance, '', 187 $anchor, $anchor_appearance), false); 188 } 189 } 190 191 function parse_interwiki($text) 192 { 193 global $InterwikiPtn; 194 195 return preg_replace("/(^|[^A-Za-z])($InterwikiPtn)(?=\$|[^\\/=&~A-Za-z0-9])/e", 196 "q1('\\1').interwiki_token(q1('\\3'),q1('\\4')).q1('\\5')", 197 $text, -1); 198 } 199 200 function interwiki_token($prefix, $ref) 201 { 202 global $pagestore; 203 204 if(($url = $pagestore->interwiki($prefix)) != '') 205 { 206 return new_entity(array('interwiki', $url . $ref, $prefix . ':' . $ref), false); 207 } 208 209 return $prefix . ':' . $ref; 210 } 211 212 function parse_hyperlink_ref($text) 213 { 214 global $UrlPtn,$InterwikiPtn; 215 216 return preg_replace("/\\[($UrlPtn|$InterwikiPtn)]/Ue", 217 "url_token(q1('\\1'), '')", $text, -1); 218 } 219 function image_search($text) 220 { 221 global $ImgPtn, $ExtRef; 222 if (preg_match("/$ImgPtn$/", $text)) 223 { return parse_elements(parse_hyperlink($text)); } 224 else 225 { return $ExtRef[0] . $text . $ExtRef[1]; } 226 } 227 function parse_hyperlink_description($text) 228 { 229 global $UrlPtn, $InterwikiPtn; 230 return preg_replace("/\\[($UrlPtn|$InterwikiPtn) ([^]]+)]/e", 231 "url_token(q1('\\1'),image_search(q1('\\4')))", 232 $text, -1); 233 } 234 235 function parse_hyperlink($text) 236 { 237 global $UrlPtn, $InterwikiPtn; 238 239 return preg_replace("/(^|[^A-Za-z])($UrlPtn|$InterwikiPtn)(?=\$|[^\\/?=&~A-Za-z0-9])/e", 240 "q1('\\1').url_token(q1('\\2'), q1('\\2')).q1('\\5')", $text, -1); 241 } 242 243 function url_token($value, $display) 244 { 245 global $pagestore, $InterwikiPtn, $UrlPtn, $RefList, $ImgPtn; 246 static $count = 1; 247 // Expand interwiki-entry, if necessary 248 if ((!preg_match("/$UrlPtn/", $value)) and 249 preg_match("/$InterwikiPtn/", $value, $match)) 250 { 251 $couldBeImage=($display==$value); 252 if (($url=$pagestore->interwiki($match[1])) != '') 253 { $value = $url . $match[2]; 254 if ($couldBeImage and preg_match("/$ImgPtn$/", $value)) 255 { $display = $value; } 256 } 257 else 258 { return $value; } 259 } 260 261 if($display == '') 262 { $display = '[' . $count++ . ']'; 263 $RefList[] = $value; } 264 265 return new_entity(array('url', $value, $display), false); 266 } 267 268 function parse_macros($text) 269 { 270 return preg_replace('/\\[\\[([^] ]+( [^]]+)?)]]/e', 271 "macro_token(q1('\\1'), q1('\\3'))", $text, -1); 272 273 } 274 275 function parse_no_macros($text) 276 { 277 return preg_replace('/\\[\\[([^] ]+( [^]]+)?)]]/e', 278 "", $text, -1); 279 280 } 281 282 function macro_token($macro, $trail) 283 { 284 global $ViewMacroEngine; 285 286 $cmd = strtok($macro, ' '); 287 $args = strtok(''); 288 289 if($ViewMacroEngine[$cmd] != '') 290 { 291 if ($cmd == 'Anchor') 292 { return new_entity(array('raw', $ViewMacroEngine[$cmd]($args)), 0); } 293 else 294 { return new_entity(array('raw', $ViewMacroEngine[$cmd]($args)), 1); } 295 } 296 else 297 { return '[[' . $macro . ']]' . ($trail == "\n" ? $trail : ''); } 298 } 299 300 function parse_transclude($text) 301 { 302 $text2 = preg_replace('/%%([^%]+)%%/e', 303 "transclude_token(q1('\\1'))", $text, -1); 304 if($text2 != $text) 305 { $text2 = str_replace("\n", '', $text2); } 306 return $text2; 307 } 308 309 function transclude_token($text) 310 { 311 global $pagestore, $ParseEngine, $ParseObject; 312 static $visited_array = array(); 313 static $visited_count = 0; 314 315 if(!validate_page($text)) 316 { return '%%' . $text . '%%'; } 317 318 $visited_array[$visited_count++] = $ParseObject; 319 for($i = 0; $i < $visited_count; $i++) 320 { 321 if($visited_array[$i] == $text) 322 { 323 $visited_count--; 324 return '%%' . $text . '%%'; 325 } 326 } 327 328 $pg = $pagestore->page($text); 329 $pg->read(); 330 if(!$pg->exists) 331 { 332 $visited_count--; 333 return '%%' . $text . '%%'; 334 } 335 336 $result = new_entity(array('raw', parseText($pg->text, $ParseEngine, $text))); 337 $visited_count--; 338 return $result; 339 } 340 341 function parse_textenhance($text) 342 { 343 global $EnableTextEnhance; 344 345 if ($EnableTextEnhance) 346 { 347 if (preg_match("/^(\*+)([^*].*)$/", $text, $match)) 348 { 349 // Special case, since *'s at start of line is markup for lists 350 $return = $match[1] . 351 preg_replace("/(\*\*)(.+)\\1/Ue", 352 "pair_tokens('bold', q1('\\2'))", $match[2], -1); 353 } 354 else 355 { 356 $return = preg_replace("/(\*\*)(.+)\\1/Ue", 357 "pair_tokens('bold', q1('\\2'))", $text, -1); 358 } 359 $return = preg_replace( "/(\/\/)(.+)\\1/Ue", 360 "pair_tokens('italic', q1('\\2'))", $return, -1); 361 $return = preg_replace( "/(--)(.+)\\1/Ue", 362 "pair_tokens('del', q1('\\2'))", $return, -1); 363 $return = preg_replace( "/(\+\+)(.+)\\1/Ue", 364 "pair_tokens('ins', q1('\\2'))", $return, -1); 365 $return = preg_replace( "/(\^\^)(.+)\\1/Ue", 366 "pair_tokens('superscript', q1('\\2'))", $return, -1); 367 $return = preg_replace( "/(,,)(.+)\\1/Ue", 368 "pair_tokens('subscript', q1('\\2'))", $return, -1); 369 return $return; 370 } else { 371 return $text; 372 } 373 } 374 375 function parse_bold($text) 376 { 377 return preg_replace("/'''(()|[^'].*)'''/Ue", "pair_tokens('bold', q1('\\1'))", 378 $text, -1); 379 } 380 381 function parse_italic($text) 382 { 383 return preg_replace("/''(()|[^'].*)''/Ue", "pair_tokens('italic', q1('\\1'))", 384 $text, -1); 385 } 386 387 function parse_teletype($text) 388 { 389 return preg_replace("/{{({*?.*}*?)}}/Ue", 390 "pair_tokens('tt', q1('\\1'))", $text, -1); 391 } 392 393 function pair_tokens($type, $text, $blockElem=false) 394 { 395 global $Entity, $FlgChr; 396 397 $Entity[count($Entity)] = array($type . '_start'); 398 $Entity[count($Entity)] = array($type . '_end'); 399 400 return $FlgChr . ($blockElem ? '' : '!') . (count($Entity) - 2) . $FlgChr . $text . 401 $FlgChr . ($blockElem ? '' : '!') . (count($Entity) - 1) . $FlgChr; 402 } 403 404 function parse_newline($text) 405 { 406 global $FlgChr; 407 static $last = array('', ''); 408 409 // More than two consecutive newlines fold into two newlines. 410 if($last[0] == "\n" && $last[1] == "\n" && $text == "\n") 411 { return ''; } 412 $last[0] = $last[1]; 413 $last[1] = $text; 414 415 // Lines not beginning with $FlgChr or beginning with $FlgChr! are paragraps 416 return preg_replace("/^(([^$FlgChr]|$FlgChr!).+)$/e", 417 "pair_tokens('paragraph', q1('\\1'), true)", $text, -1); 418 } 419 420 function parse_horiz($text) 421 { 422 return preg_replace("/-{4,}(\\n(\\r)?)?/e", "new_entity(array('hr'))", 423 $text, -1); 424 } 425 426 function parse_nowiki($text) 427 { 428 return preg_replace("/```(.*)```/Ue", 429 "new_entity(array('nowiki', parse_elements(q1('\\1'))))", 430 $text, -1); 431 } 432 433 function parse_raw_html($text) 434 { 435 global $Entity, $FlgChr; 436 static $in_html = 0; 437 static $buffer = ''; 438 439 if($in_html) 440 { 441 if(strtolower($text) == "</html>\n") 442 { 443 $Entity[count($Entity)] = array('raw', $buffer); 444 $buffer = ''; 445 $in_html = 0; 446 return $FlgChr . (count($Entity) - 1) . $FlgChr; //$blockElem=true 447 } 448 449 $buffer = $buffer . parse_elements($text); 450 return ''; 451 } 452 else 453 { 454 if(strtolower($text) == "<html>\n") 455 { 456 $in_html = 1; 457 return ''; 458 } 459 460 return $text; 461 } 462 } 463 464 function parse_indents($text) 465 { 466 global $MaxNesting; 467 static $last_prefix = ''; // Last line's prefix. 468 469 // Locate the indent prefix characters. 470 471 preg_match('/^([:\\*#]*)(;([^:]*):)?(.*\\n?$)/', $text, $result); 472 473 if($result[2] != '') // Definition list. 474 { $result[1] = $result[1] . ';'; } 475 476 // No list on last line, no list on this line. Bail out: 477 478 if($last_prefix == '' && $result[1] == '') 479 { return $text; } // Common case fast. 480 481 // Remember lengths of strings. 482 483 $last_len = strlen($last_prefix); 484 $prefix_len = strlen($result[1]); 485 486 $text = $result[4]; 487 488 $fixup = ''; 489 490 // Loop through and look for prefix characters in common with the 491 // previous line. 492 493 for($i = 0; 494 $i < $MaxNesting && ($i < $last_len || $i < $prefix_len); 495 $i++) 496 { 497 // If equal, continue. 498 if($i < $last_len && $i < $prefix_len // Neither past end. 499 && $last_prefix[$i] == $result[1][$i]) // Equal content. 500 { continue; } 501 502 // If we've gone deeper than the previous line, we're done. 503 if($i >= $last_len) 504 { break; } 505 506 // If last line goes further than us, end its dangling lists. 507 if($i >= $prefix_len // Current line ended. 508 || $last_prefix[$i] != $result[1][$i]) // Or not, but they differ. 509 { 510 for($j = $i; $j < $MaxNesting && $j < $last_len; $j++) 511 { 512 $fixup = entity_listitem($last_prefix[$j], 'end') 513 . entity_list($last_prefix[$j], 'end') 514 . $fixup; 515 } 516 break; 517 } 518 } 519 520 // End the preceding line's list item if we're starting another one 521 // at the same level. 522 523 if($i > 0 && $i >= $prefix_len) 524 { $fixup = $fixup . entity_listitem($last_prefix[$i - 1], 'end'); } 525 526 // Start fresh new lists for this line as needed. 527 // We start all but the last one as *indents* (definition lists) 528 // instead of what they really may appear as, since their function is 529 // really just to indent. 530 531 for(; $i < $MaxNesting - 1 && $i + 1 < $prefix_len; $i++) 532 { 533 $result[1][$i] = ':'; // Pretend to be an indent. 534 $fixup = $fixup 535 . entity_list(':', 'start') 536 . entity_listitem(':', 'start'); 537 } 538 if($i < $prefix_len) // Start the list itself. 539 { 540 $fixup = $fixup 541 . entity_list($result[1][$i], 'start'); 542 } 543 544 // Start the list *item*. 545 546 if($result[2] != '') // Definition list. 547 { 548 $fixup = $fixup 549 . new_entity(array('term_item_start')) 550 . $result[3] 551 . new_entity(array('term_item_end')); 552 } 553 554 if($result[1] != '') 555 { $text = entity_listitem(substr($result[1], -1), 'start') . $text; } 556 557 $text = $fixup . $text; 558 559 $last_prefix = $result[1]; 560 561 return $text; 562 } 563 564 function entity_list($type, $fn, $attr='') 565 { 566 if($type == '*') 567 { return new_entity(array('bullet_list_' . $fn, $attr)); } 568 else if($type == ':' || $type == ';') 569 { return new_entity(array('indent_list_' . $fn)); } 570 else if($type == '#') 571 { return new_entity(array('numbered_list_' . $fn)); } 572 } 573 574 function entity_listitem($type, $fn) 575 { 576 if($type == '*') 577 { return new_entity(array('bullet_item_' . $fn)); } 578 else if($type == ':' || $type == ';') 579 { return new_entity(array('indent_item_' . $fn)); } 580 else if($type == '#') 581 { return new_entity(array('numbered_item_' . $fn)); } 582 } 583 584 function parse_heading($text) 585 { 586 global $MaxHeading, $HeadingOffset; 587 588 if(!preg_match('/^\s*(=+) (.*) (=+)\s*$/', $text, $result)) 589 { return $text; } 590 591 if(strlen($result[1]) != strlen($result[3])) 592 { return $text; } 593 594 $level = strlen($result[1]) + $HeadingOffset; 595 if($level > $MaxHeading) 596 { $level = $MaxHeading; } 597 598 return new_entity(array('head_start', $level)) . 599 $result[2] . 600 new_entity(array('head_end', $level)); 601 } 602 603 function parse_htmlisms($text) 604 { 605 $text = str_replace('&', '&', $text); 606 $text = str_replace('<', '<', $text); 607 return $text; 608 } 609 610 function parse_elements($text) 611 { 612 global $FlgChr; 613 return preg_replace("/$FlgChr!?(\\d+)$FlgChr/e", "generate_element(q1('\\1'))", $text, -1); 614 } 615 616 function generate_element($text) 617 { 618 global $Entity, $DisplayEngine; 619 620 if(function_exists('call_user_func_array')) 621 { 622 return call_user_func_array($DisplayEngine[$Entity[$text][0]], 623 array_slice($Entity[$text], 1)); 624 } 625 else 626 { 627 return $DisplayEngine[$Entity[$text][0]]($Entity[$text][1], 628 $Entity[$text][2], 629 $Entity[$text][3], 630 $Entity[$text][4], 631 $Entity[$text][5]); 632 } 633 } 634 635 function parse_diff_skip($text) 636 { 637 if(preg_match('/^--+/', $text)) 638 { return ''; } 639 if(preg_match('/^\\\\ No newline/', $text)) 640 { return ''; } 641 return $text; 642 } 643 644 function parse_diff_color($text) 645 { 646 static $in_old = 0; 647 static $in_new = 0; 648 649 if(strlen($text) == 0) 650 { $this_old = $this_new = 0; } 651 else 652 { 653 $this_old = ($text[0] == '<'); 654 $this_new = ($text[0] == '>'); 655 } 656 657 if($this_old || $this_new) 658 { $text = substr($text, 2); } 659 660 if($this_old && !$in_old) 661 { $text = new_entity(array('diff_old_start')) . $text; } 662 else if($this_new && !$in_new) 663 { $text = new_entity(array('diff_new_start')) . $text; } 664 665 if($in_old && !$this_old) 666 { $text = new_entity(array('diff_old_end')) . $text; } 667 else if($in_new && !$this_new) 668 { $text = new_entity(array('diff_new_end')) . $text; } 669 670 $in_old = $this_old; 671 $in_new = $this_new; 672 673 return $text; 674 } 675 676 function parse_diff_message($text) 677 { 678 global $FlgChr; 679 680 $text = preg_replace('/(^(' . $FlgChr . '\\d+' . $FlgChr . ')?)\\d[0-9,]*c[0-9,]*$/e', 681 "q1('\\1').new_entity(array('diff_change'))", $text, -1); 682 $text = preg_replace('/(^(' . $FlgChr . '\\d+' . $FlgChr . ')?)\\d[0-9,]*a[0-9,]*$/e', 683 "q1('\\1').new_entity(array('diff_add'))", $text, -1); 684 $text = preg_replace('/(^(' . $FlgChr . '\\d+' . $FlgChr . ')?)\\d[0-9,]*d[0-9,]*$/e', 685 "q1('\\1').new_entity(array('diff_delete'))", $text, -1); 686 687 return $text; 688 } 689 690 function parse_table($text) 691 { 692 static $in_table = 0; 693 694 $pre = ''; 695 $post = ''; 696 if(preg_match('/^(\|\|)+(\{([^{}]+)\})?.*(\|\|)\s*$/', $text, $args)) // Table. 697 { 698 if(!$in_table) 699 { 700 $pre = html_table_start($args[3]); 701 $in_table = 1; 702 } 703 $text = preg_replace('/\|\s+\|/e', 704 "q1('|').new_entity(array('raw',' ')).q1('|')", 705 $text); 706 $text = preg_replace('/^((\|\|+)(\{([^{}]+)\})?)(.*)\|\|\s*$/e', 707 "new_entity(array('raw',html_table_row_start('\\4'). 708 html_table_cell_start(strlen('\\2')/2, '\\4'))).". 709 "q1('\\5').new_entity(array('raw',html_table_cell_end().html_table_row_end()))", 710 $text, -1); 711 $text = preg_replace('/((\|\|+)(\{([^{}]+)\})?)/e', 712 "new_entity(array('raw',html_table_cell_end().html_table_cell_start(strlen('\\2')/2, '\\4')))", 713 $text, -1); 714 } 715 else if($in_table) // Have exited table. 716 { 717 $in_table = 0; 718 $pre = html_table_end(); 719 } 720 721 if($pre != '') 722 { $text = new_entity(array('raw', $pre)) . $text; } 723 if($post != '') 724 { $text = $text . new_entity(array('raw', $post)); } 725 726 return $text; 727 } 728 ?>
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| [ Powered by PHPXref - Served by Debian GNU/Linux ] |