[ Index ] |
|
Code source de Phorum 5.1.25 |
1 <?php 2 3 # 4 # Markdown - A text-to-HTML conversion tool for web writers 5 # 6 # Copyright (c) 2004-2005 John Gruber 7 # <http://daringfireball.net/projects/markdown/> 8 # 9 # Copyright (c) 2004-2005 Michel Fortin - PHP Port 10 # <http://www.michelf.com/projects/php-markdown/> 11 # 12 13 14 global $MarkdownPHPVersion, $MarkdownSyntaxVersion, 15 $md_empty_element_suffix, $md_tab_width, 16 $md_nested_brackets_depth, $md_nested_brackets, 17 $md_escape_table, $md_backslash_escape_table, 18 $md_list_level; 19 20 $MarkdownPHPVersion = '1.0.1c'; # Fri 9 Dec 2005 21 $MarkdownSyntaxVersion = '1.0.1'; # Sun 12 Dec 2004 22 23 24 # 25 # Global default settings: 26 # 27 $md_empty_element_suffix = " />"; # Change to ">" for HTML output 28 $md_tab_width = 4; 29 30 # 31 # WordPress settings: 32 # 33 $md_wp_posts = true; # Set to false to remove Markdown from posts. 34 $md_wp_comments = true; # Set to false to remove Markdown from comments. 35 36 37 # -- WordPress Plugin Interface ----------------------------------------------- 38 /* 39 Plugin Name: Markdown 40 Plugin URI: http://www.michelf.com/projects/php-markdown/ 41 Description: <a href="http://daringfireball.net/projects/markdown/syntax">Markdown syntax</a> allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by <a href="http://daringfireball.net/">John Gruber</a>. <a href="http://www.michelf.com/projects/php-markdown/">More...</a> 42 Version: 1.0.1c 43 Author: Michel Fortin 44 Author URI: http://www.michelf.com/ 45 */ 46 if (isset($wp_version)) { 47 # More details about how it works here: 48 # <http://www.michelf.com/weblog/2005/wordpress-text-flow-vs-markdown/> 49 50 # Post content and excerpts 51 if ($md_wp_posts) { 52 remove_filter('the_content', 'wpautop'); 53 remove_filter('the_excerpt', 'wpautop'); 54 add_filter('the_content', 'Markdown', 6); 55 add_filter('get_the_excerpt', 'Markdown', 6); 56 add_filter('get_the_excerpt', 'trim', 7); 57 add_filter('the_excerpt', 'md_add_p'); 58 add_filter('the_excerpt_rss', 'md_strip_p'); 59 60 remove_filter('content_save_pre', 'balanceTags', 50); 61 remove_filter('excerpt_save_pre', 'balanceTags', 50); 62 add_filter('the_content', 'balanceTags', 50); 63 add_filter('get_the_excerpt', 'balanceTags', 9); 64 65 function md_add_p($text) { 66 if (strlen($text) == 0) return; 67 if (strcasecmp(substr($text, -3), '<p>') == 0) return $text; 68 return '<p>'.$text.'</p>'; 69 } 70 function md_strip_p($t) { return preg_replace('{</?[pP]>}', '', $t); } 71 } 72 73 # Comments 74 if ($md_wp_comments) { 75 remove_filter('comment_text', 'wpautop'); 76 remove_filter('comment_text', 'make_clickable'); 77 add_filter('pre_comment_content', 'Markdown', 6); 78 add_filter('pre_comment_content', 'md_hide_tags', 8); 79 add_filter('pre_comment_content', 'md_show_tags', 12); 80 add_filter('get_comment_text', 'Markdown', 6); 81 add_filter('get_comment_excerpt', 'Markdown', 6); 82 add_filter('get_comment_excerpt', 'md_strip_p', 7); 83 84 global $md_hidden_tags; 85 $md_hidden_tags = array( 86 '<p>' => md5('<p>'), '</p>' => md5('</p>'), 87 '<pre>' => md5('<pre>'), '</pre>'=> md5('</pre>'), 88 '<ol>' => md5('<ol>'), '</ol>' => md5('</ol>'), 89 '<ul>' => md5('<ul>'), '</ul>' => md5('</ul>'), 90 '<li>' => md5('<li>'), '</li>' => md5('</li>'), 91 ); 92 93 function md_hide_tags($text) { 94 global $md_hidden_tags; 95 return str_replace(array_keys($md_hidden_tags), 96 array_values($md_hidden_tags), $text); 97 } 98 function md_show_tags($text) { 99 global $md_hidden_tags; 100 return str_replace(array_values($md_hidden_tags), 101 array_keys($md_hidden_tags), $text); 102 } 103 } 104 } 105 106 107 # -- bBlog Plugin Info -------------------------------------------------------- 108 function identify_modifier_markdown() { 109 global $MarkdownPHPVersion; 110 return array( 111 'name' => 'markdown', 112 'type' => 'modifier', 113 'nicename' => 'Markdown', 114 'description' => 'A text-to-HTML conversion tool for web writers', 115 'authors' => 'Michel Fortin and John Gruber', 116 'licence' => 'GPL', 117 'version' => $MarkdownPHPVersion, 118 'help' => '<a href="http://daringfireball.net/projects/markdown/syntax">Markdown syntax</a> allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by <a href="http://daringfireball.net/">John Gruber</a>. <a href="http://www.michelf.com/projects/php-markdown/">More...</a>' 119 ); 120 } 121 122 # -- Smarty Modifier Interface ------------------------------------------------ 123 function smarty_modifier_markdown($text) { 124 return Markdown($text); 125 } 126 127 # -- Textile Compatibility Mode ----------------------------------------------- 128 # Rename this file to "classTextile.php" and it can replace Textile anywhere. 129 if (strcasecmp(substr(__FILE__, -16), "classTextile.php") == 0) { 130 # Try to include PHP SmartyPants. Should be in the same directory. 131 @include_once 'smartypants.php'; 132 # Fake Textile class. It calls Markdown instead. 133 class Textile { 134 function TextileThis($text, $lite='', $encode='', $noimage='', $strict='') { 135 if ($lite == '' && $encode == '') $text = Markdown($text); 136 if (function_exists('SmartyPants')) $text = SmartyPants($text); 137 return $text; 138 } 139 } 140 } 141 142 143 # -- Phorum Module Info -------------------------------------------------------- 144 /* phorum module info 145 hook: format|phorum_Markdown 146 hook: quote|phorum_Markdown_quote 147 title: Markdown 148 desc: Markdown syntax allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by John Gruber.<br />http://daringfireball.net/projects/markdown/syntax<br />http://www.michelf.com/projects/php-markdown/ 149 */ 150 151 include_once "./include/format_functions.php"; 152 153 function phorum_Markdown ($data) 154 { 155 foreach($data as $key=>$message){ 156 if(!empty($message["body"])){ 157 $message["body"] = str_replace(array("<phorum break>", "&","<",">"), array("", "&","<",">"), $message["body"]); 158 $data[$key]["body"] = Markdown($message["body"]); 159 } 160 } 161 162 // Make HTML safe. Markdown does not handle stripping evil code. 163 // We call the html mod's formatting code directly here, so we 164 // do not have to worry about module ordering. 165 require_once ('./mods/html/html.php'); 166 $data = phorum_html($data); 167 168 return ($data); 169 } 170 171 function phorum_Markdown_quote ($message) 172 { 173 $PHORUM = $GLOBALS["PHORUM"]; 174 175 $author = "\n> **".$message[0] . ' ' . $PHORUM['DATA']['LANG']['Wrote'] . ':**'; 176 $quote = phorum_strip_body($message[1]); 177 $quote = str_replace("\n", "\n> ", $quote); 178 $quote = wordwrap(trim($quote), 50, "\n> ", true); 179 $quote = $author . "\n> \n> " . $quote; 180 181 return $quote; 182 } 183 184 185 # 186 # Globals: 187 # 188 189 # Regex to match balanced [brackets]. 190 # Needed to insert a maximum bracked depth while converting to PHP. 191 $md_nested_brackets_depth = 6; 192 $md_nested_brackets = 193 str_repeat('(?>[^\[\]]+|\[', $md_nested_brackets_depth). 194 str_repeat('\])*', $md_nested_brackets_depth); 195 196 # Table of hash values for escaped characters: 197 $md_escape_table = array( 198 "\\" => md5("\\"), 199 "`" => md5("`"), 200 "*" => md5("*"), 201 "_" => md5("_"), 202 "{" => md5("{"), 203 "}" => md5("}"), 204 "[" => md5("["), 205 "]" => md5("]"), 206 "(" => md5("("), 207 ")" => md5(")"), 208 ">" => md5(">"), 209 "#" => md5("#"), 210 "+" => md5("+"), 211 "-" => md5("-"), 212 "." => md5("."), 213 "!" => md5("!") 214 ); 215 # Create an identical table but for escaped characters. 216 $md_backslash_escape_table; 217 foreach ($md_escape_table as $key => $char) 218 $md_backslash_escape_table["\\$key"] = $char; 219 220 221 function Markdown($text) { 222 # 223 # Main function. The order in which other subs are called here is 224 # essential. Link and image substitutions need to happen before 225 # _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the <a> 226 # and <img> tags get encoded. 227 # 228 # Clear the global hashes. If we don't clear these, you get conflicts 229 # from other articles when generating a page which contains more than 230 # one article (e.g. an index page that shows the N most recent 231 # articles): 232 global $md_urls, $md_titles, $md_html_blocks; 233 $md_urls = array(); 234 $md_titles = array(); 235 $md_html_blocks = array(); 236 237 # Standardize line endings: 238 # DOS to Unix and Mac to Unix 239 $text = str_replace(array("\r\n", "\r"), "\n", $text); 240 241 # Make sure $text ends with a couple of newlines: 242 $text .= "\n\n"; 243 244 # Convert all tabs to spaces. 245 $text = _Detab($text); 246 247 # Strip any lines consisting only of spaces and tabs. 248 # This makes subsequent regexen easier to write, because we can 249 # match consecutive blank lines with /\n+/ instead of something 250 # contorted like /[ \t]*\n+/ . 251 $text = preg_replace('/^[ \t]+$/m', '', $text); 252 253 # Turn block-level HTML blocks into hash entries 254 $text = _HashHTMLBlocks($text); 255 256 # Strip link definitions, store in hashes. 257 $text = _StripLinkDefinitions($text); 258 259 $text = _RunBlockGamut($text); 260 261 $text = _UnescapeSpecialChars($text); 262 263 return $text . "\n"; 264 } 265 266 267 function _StripLinkDefinitions($text) { 268 # 269 # Strips link definitions from text, stores the URLs and titles in 270 # hash references. 271 # 272 global $md_tab_width; 273 $less_than_tab = $md_tab_width - 1; 274 275 # Link defs are in the form: ^[id]: url "optional title" 276 $text = preg_replace_callback('{ 277 ^[ ]{0,'.$less_than_tab.'}\[(.+)\]: # id = $1 278 [ \t]* 279 \n? # maybe *one* newline 280 [ \t]* 281 <?(\S+?)>? # url = $2 282 [ \t]* 283 \n? # maybe one newline 284 [ \t]* 285 (?: 286 (?<=\s) # lookbehind for whitespace 287 ["(] 288 (.+?) # title = $3 289 [")] 290 [ \t]* 291 )? # title is optional 292 (?:\n+|\Z) 293 }xm', 294 '_StripLinkDefinitions_callback', 295 $text); 296 return $text; 297 } 298 function _StripLinkDefinitions_callback($matches) { 299 global $md_urls, $md_titles; 300 $link_id = strtolower($matches[1]); 301 $md_urls[$link_id] = _EncodeAmpsAndAngles($matches[2]); 302 if (isset($matches[3])) 303 $md_titles[$link_id] = str_replace('"', '"', $matches[3]); 304 return ''; # String that will replace the block 305 } 306 307 308 function _HashHTMLBlocks($text) { 309 global $md_tab_width; 310 $less_than_tab = $md_tab_width - 1; 311 312 # Hashify HTML blocks: 313 # We only want to do this for block-level HTML tags, such as headers, 314 # lists, and tables. That's because we still want to wrap <p>s around 315 # "paragraphs" that are wrapped in non-block-level tags, such as anchors, 316 # phrase emphasis, and spans. The list of tags we're looking for is 317 # hard-coded: 318 $block_tags_a = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|'. 319 'script|noscript|form|fieldset|iframe|math|ins|del'; 320 $block_tags_b = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|'. 321 'script|noscript|form|fieldset|iframe|math'; 322 323 # First, look for nested blocks, e.g.: 324 # <div> 325 # <div> 326 # tags for inner block must be indented. 327 # </div> 328 # </div> 329 # 330 # The outermost tags must start at the left margin for this to match, and 331 # the inner nested divs must be indented. 332 # We need to do this before the next, more liberal match, because the next 333 # match will start at the first `<div>` and stop at the first `</div>`. 334 $text = preg_replace_callback("{ 335 ( # save in $1 336 ^ # start of line (with /m) 337 <($block_tags_a) # start tag = $2 338 \\b # word break 339 (.*\\n)*? # any number of lines, minimally matching 340 </\\2> # the matching end tag 341 [ \\t]* # trailing spaces/tabs 342 (?=\\n+|\\Z) # followed by a newline or end of document 343 ) 344 }xm", 345 '_HashHTMLBlocks_callback', 346 $text); 347 348 # 349 # Now match more liberally, simply from `\n<tag>` to `</tag>\n` 350 # 351 $text = preg_replace_callback("{ 352 ( # save in $1 353 ^ # start of line (with /m) 354 <($block_tags_b) # start tag = $2 355 \\b # word break 356 (.*\\n)*? # any number of lines, minimally matching 357 .*</\\2> # the matching end tag 358 [ \\t]* # trailing spaces/tabs 359 (?=\\n+|\\Z) # followed by a newline or end of document 360 ) 361 }xm", 362 '_HashHTMLBlocks_callback', 363 $text); 364 365 # Special case just for <hr />. It was easier to make a special case than 366 # to make the other regex more complicated. 367 $text = preg_replace_callback('{ 368 (?: 369 (?<=\n\n) # Starting after a blank line 370 | # or 371 \A\n? # the beginning of the doc 372 ) 373 ( # save in $1 374 [ ]{0,'.$less_than_tab.'} 375 <(hr) # start tag = $2 376 \b # word break 377 ([^<>])*? # 378 /?> # the matching end tag 379 [ \t]* 380 (?=\n{2,}|\Z) # followed by a blank line or end of document 381 ) 382 }x', 383 '_HashHTMLBlocks_callback', 384 $text); 385 386 # Special case for standalone HTML comments: 387 $text = preg_replace_callback('{ 388 (?: 389 (?<=\n\n) # Starting after a blank line 390 | # or 391 \A\n? # the beginning of the doc 392 ) 393 ( # save in $1 394 [ ]{0,'.$less_than_tab.'} 395 (?s: 396 <! 397 (--.*?--\s*)+ 398 > 399 ) 400 [ \t]* 401 (?=\n{2,}|\Z) # followed by a blank line or end of document 402 ) 403 }x', 404 '_HashHTMLBlocks_callback', 405 $text); 406 407 return $text; 408 } 409 function _HashHTMLBlocks_callback($matches) { 410 global $md_html_blocks; 411 $text = $matches[1]; 412 $key = md5($text); 413 $md_html_blocks[$key] = $text; 414 return "\n\n$key\n\n"; # String that will replace the block 415 } 416 417 418 function _RunBlockGamut($text) { 419 # 420 # These are all the transformations that form block-level 421 # tags like paragraphs, headers, and list items. 422 # 423 global $md_empty_element_suffix; 424 425 $text = _DoHeaders($text); 426 427 # Do Horizontal Rules: 428 $text = preg_replace( 429 array('{^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$}mx', 430 '{^[ ]{0,2}([ ]? -[ ]?){3,}[ \t]*$}mx', 431 '{^[ ]{0,2}([ ]? _[ ]?){3,}[ \t]*$}mx'), 432 "\n<hr$md_empty_element_suffix\n", 433 $text); 434 435 $text = _DoLists($text); 436 $text = _DoCodeBlocks($text); 437 $text = _DoBlockQuotes($text); 438 439 # We already ran _HashHTMLBlocks() before, in Markdown(), but that 440 # was to escape raw HTML in the original Markdown source. This time, 441 # we're escaping the markup we've just created, so that we don't wrap 442 # <p> tags around block-level tags. 443 $text = _HashHTMLBlocks($text); 444 $text = _FormParagraphs($text); 445 446 return $text; 447 } 448 449 450 function _RunSpanGamut($text) { 451 # 452 # These are all the transformations that occur *within* block-level 453 # tags like paragraphs, headers, and list items. 454 # 455 global $md_empty_element_suffix; 456 457 $text = _DoCodeSpans($text); 458 459 $text = _EscapeSpecialChars($text); 460 461 # Process anchor and image tags. Images must come first, 462 # because ![foo][f] looks like an anchor. 463 $text = _DoImages($text); 464 $text = _DoAnchors($text); 465 466 # Make links out of things like `<http://example.com/>` 467 # Must come after _DoAnchors(), because you can use < and > 468 # delimiters in inline links like [this](<url>). 469 $text = _DoAutoLinks($text); 470 $text = _EncodeAmpsAndAngles($text); 471 $text = _DoItalicsAndBold($text); 472 473 # Do hard breaks: 474 $text = preg_replace('/ {2,}\n/', "<br$md_empty_element_suffix\n", $text); 475 476 return $text; 477 } 478 479 480 function _EscapeSpecialChars($text) { 481 global $md_escape_table; 482 $tokens = _TokenizeHTML($text); 483 484 $text = ''; # rebuild $text from the tokens 485 # $in_pre = 0; # Keep track of when we're inside <pre> or <code> tags. 486 # $tags_to_skip = "!<(/?)(?:pre|code|kbd|script|math)[\s>]!"; 487 488 foreach ($tokens as $cur_token) { 489 if ($cur_token[0] == 'tag') { 490 # Within tags, encode * and _ so they don't conflict 491 # with their use in Markdown for italics and strong. 492 # We're replacing each such character with its 493 # corresponding MD5 checksum value; this is likely 494 # overkill, but it should prevent us from colliding 495 # with the escape values by accident. 496 $cur_token[1] = str_replace(array('*', '_'), 497 array($md_escape_table['*'], $md_escape_table['_']), 498 $cur_token[1]); 499 $text .= $cur_token[1]; 500 } else { 501 $t = $cur_token[1]; 502 $t = _EncodeBackslashEscapes($t); 503 $text .= $t; 504 } 505 } 506 return $text; 507 } 508 509 510 function _DoAnchors($text) { 511 # 512 # Turn Markdown link shortcuts into XHTML <a> tags. 513 # 514 global $md_nested_brackets; 515 # 516 # First, handle reference-style links: [link text] [id] 517 # 518 $text = preg_replace_callback("{ 519 ( # wrap whole match in $1 520 \\[ 521 ($md_nested_brackets) # link text = $2 522 \\] 523 524 [ ]? # one optional space 525 (?:\\n[ ]*)? # one optional newline followed by spaces 526 527 \\[ 528 (.*?) # id = $3 529 \\] 530 ) 531 }xs", 532 '_DoAnchors_reference_callback', $text); 533 534 # 535 # Next, inline-style links: [link text](url "optional title") 536 # 537 $text = preg_replace_callback("{ 538 ( # wrap whole match in $1 539 \\[ 540 ($md_nested_brackets) # link text = $2 541 \\] 542 \\( # literal paren 543 [ \\t]* 544 <?(.*?)>? # href = $3 545 [ \\t]* 546 ( # $4 547 (['\"]) # quote char = $5 548 (.*?) # Title = $6 549 \\5 # matching quote 550 )? # title is optional 551 \\) 552 ) 553 }xs", 554 '_DoAnchors_inline_callback', $text); 555 556 return $text; 557 } 558 function _DoAnchors_reference_callback($matches) { 559 global $md_urls, $md_titles, $md_escape_table; 560 $whole_match = $matches[1]; 561 $link_text = $matches[2]; 562 $link_id = strtolower($matches[3]); 563 564 if ($link_id == "") { 565 $link_id = strtolower($link_text); # for shortcut links like [this][]. 566 } 567 568 if (isset($md_urls[$link_id])) { 569 $url = $md_urls[$link_id]; 570 # We've got to encode these to avoid conflicting with italics/bold. 571 $url = str_replace(array('*', '_'), 572 array($md_escape_table['*'], $md_escape_table['_']), 573 $url); 574 $result = "<a href=\"$url\""; 575 if ( isset( $md_titles[$link_id] ) ) { 576 $title = $md_titles[$link_id]; 577 $title = str_replace(array('*', '_'), 578 array($md_escape_table['*'], 579 $md_escape_table['_']), $title); 580 $result .= " title=\"$title\""; 581 } 582 $result .= ">$link_text</a>"; 583 } 584 else { 585 $result = $whole_match; 586 } 587 return $result; 588 } 589 function _DoAnchors_inline_callback($matches) { 590 global $md_escape_table; 591 $whole_match = $matches[1]; 592 $link_text = $matches[2]; 593 $url = $matches[3]; 594 $title =& $matches[6]; 595 596 # We've got to encode these to avoid conflicting with italics/bold. 597 $url = str_replace(array('*', '_'), 598 array($md_escape_table['*'], $md_escape_table['_']), 599 $url); 600 $result = "<a href=\"$url\""; 601 if (isset($title)) { 602 $title = str_replace('"', '"', $title); 603 $title = str_replace(array('*', '_'), 604 array($md_escape_table['*'], $md_escape_table['_']), 605 $title); 606 $result .= " title=\"$title\""; 607 } 608 609 $result .= ">$link_text</a>"; 610 611 return $result; 612 } 613 614 615 function _DoImages($text) { 616 # 617 # Turn Markdown image shortcuts into <img> tags. 618 # 619 global $md_nested_brackets; 620 621 # 622 # First, handle reference-style labeled images: ![alt text][id] 623 # 624 $text = preg_replace_callback('{ 625 ( # wrap whole match in $1 626 !\[ 627 ('.$md_nested_brackets.') # alt text = $2 628 \] 629 630 [ ]? # one optional space 631 (?:\n[ ]*)? # one optional newline followed by spaces 632 633 \[ 634 (.*?) # id = $3 635 \] 636 637 ) 638 }xs', 639 '_DoImages_reference_callback', $text); 640 641 # 642 # Next, handle inline images:  643 # Don't forget: encode * and _ 644 645 $text = preg_replace_callback('{ 646 ( # wrap whole match in $1 647 !\[ 648 ('.$md_nested_brackets.') # alt text = $2 649 \] 650 \( # literal paren 651 [ \t]* 652 <?(\S+?)>? # src url = $3 653 [ \t]* 654 ( # $4 655 ([\'"]) # quote char = $5 656 (.*?) # title = $6 657 \5 # matching quote 658 [ \t]* 659 )? # title is optional 660 \) 661 ) 662 }xs', 663 '_DoImages_inline_callback', $text); 664 665 return $text; 666 } 667 function _DoImages_reference_callback($matches) { 668 global $md_urls, $md_titles, $md_empty_element_suffix, $md_escape_table; 669 $whole_match = $matches[1]; 670 $alt_text = $matches[2]; 671 $link_id = strtolower($matches[3]); 672 673 if ($link_id == "") { 674 $link_id = strtolower($alt_text); # for shortcut links like ![this][]. 675 } 676 677 $alt_text = str_replace('"', '"', $alt_text); 678 if (isset($md_urls[$link_id])) { 679 $url = $md_urls[$link_id]; 680 # We've got to encode these to avoid conflicting with italics/bold. 681 $url = str_replace(array('*', '_'), 682 array($md_escape_table['*'], $md_escape_table['_']), 683 $url); 684 $result = "<img src=\"$url\" alt=\"$alt_text\""; 685 if (isset($md_titles[$link_id])) { 686 $title = $md_titles[$link_id]; 687 $title = str_replace(array('*', '_'), 688 array($md_escape_table['*'], 689 $md_escape_table['_']), $title); 690 $result .= " title=\"$title\""; 691 } 692 $result .= $md_empty_element_suffix; 693 } 694 else { 695 # If there's no such link ID, leave intact: 696 $result = $whole_match; 697 } 698 699 return $result; 700 } 701 function _DoImages_inline_callback($matches) { 702 global $md_empty_element_suffix, $md_escape_table; 703 $whole_match = $matches[1]; 704 $alt_text = $matches[2]; 705 $url = $matches[3]; 706 $title = ''; 707 if (isset($matches[6])) { 708 $title = $matches[6]; 709 } 710 711 $alt_text = str_replace('"', '"', $alt_text); 712 $title = str_replace('"', '"', $title); 713 # We've got to encode these to avoid conflicting with italics/bold. 714 $url = str_replace(array('*', '_'), 715 array($md_escape_table['*'], $md_escape_table['_']), 716 $url); 717 $result = "<img src=\"$url\" alt=\"$alt_text\""; 718 if (isset($title)) { 719 $title = str_replace(array('*', '_'), 720 array($md_escape_table['*'], $md_escape_table['_']), 721 $title); 722 $result .= " title=\"$title\""; # $title already quoted 723 } 724 $result .= $md_empty_element_suffix; 725 726 return $result; 727 } 728 729 730 function _DoHeaders($text) { 731 # Setext-style headers: 732 # Header 1 733 # ======== 734 # 735 # Header 2 736 # -------- 737 # 738 $text = preg_replace( 739 array('{ ^(.+)[ \t]*\n=+[ \t]*\n+ }emx', 740 '{ ^(.+)[ \t]*\n-+[ \t]*\n+ }emx'), 741 array("'<h1>'._RunSpanGamut(_UnslashQuotes('\\1')).'</h1>\n\n'", 742 "'<h2>'._RunSpanGamut(_UnslashQuotes('\\1')).'</h2>\n\n'"), 743 $text); 744 745 # atx-style headers: 746 # # Header 1 747 # ## Header 2 748 # ## Header 2 with closing hashes ## 749 # ... 750 # ###### Header 6 751 # 752 $text = preg_replace("{ 753 ^(\\#{1,6}) # $1 = string of #'s 754 [ \\t]* 755 (.+?) # $2 = Header text 756 [ \\t]* 757 \\#* # optional closing #'s (not counted) 758 \\n+ 759 }xme", 760 "'<h'.strlen('\\1').'>'._RunSpanGamut(_UnslashQuotes('\\2')).'</h'.strlen('\\1').'>\n\n'", 761 $text); 762 763 return $text; 764 } 765 766 767 function _DoLists($text) { 768 # 769 # Form HTML ordered (numbered) and unordered (bulleted) lists. 770 # 771 global $md_tab_width, $md_list_level; 772 $less_than_tab = $md_tab_width - 1; 773 774 # Re-usable patterns to match list item bullets and number markers: 775 $marker_ul = '[*+-]'; 776 $marker_ol = '\d+[.]'; 777 $marker_any = "(?:$marker_ul|$marker_ol)"; 778 779 $markers = array($marker_ul, $marker_ol); 780 781 foreach ($markers as $marker) { 782 # Re-usable pattern to match any entirel ul or ol list: 783 $whole_list = ' 784 ( # $1 = whole list 785 ( # $2 786 [ ]{0,'.$less_than_tab.'} 787 ('.$marker.') # $3 = first list item marker 788 [ \t]+ 789 ) 790 (?s:.+?) 791 ( # $4 792 \z 793 | 794 \n{2,} 795 (?=\S) 796 (?! # Negative lookahead for another list item marker 797 [ \t]* 798 '.$marker.'[ \t]+ 799 ) 800 ) 801 ) 802 '; // mx 803 804 # We use a different prefix before nested lists than top-level lists. 805 # See extended comment in _ProcessListItems(). 806 807 if ($md_list_level) { 808 $text = preg_replace_callback('{ 809 ^ 810 '.$whole_list.' 811 }mx', 812 '_DoLists_callback_top', $text); 813 } 814 else { 815 $text = preg_replace_callback('{ 816 (?:(?<=\n\n)|\A\n?) 817 '.$whole_list.' 818 }mx', 819 '_DoLists_callback_nested', $text); 820 } 821 } 822 823 return $text; 824 } 825 function _DoLists_callback_top($matches) { 826 # Re-usable patterns to match list item bullets and number markers: 827 $marker_ul = '[*+-]'; 828 $marker_ol = '\d+[.]'; 829 $marker_any = "(?:$marker_ul|$marker_ol)"; 830 831 $list = $matches[1]; 832 $list_type = preg_match("/$marker_ul/", $matches[3]) ? "ul" : "ol"; 833 834 $marker_any = ( $list_type == "ul" ? $marker_ul : $marker_ol ); 835 836 # Turn double returns into triple returns, so that we can make a 837 # paragraph for the last item in a list, if necessary: 838 $list = preg_replace("/\n{2,}/", "\n\n\n", $list); 839 $result = _ProcessListItems($list, $marker_any); 840 841 # Trim any trailing whitespace, to put the closing `</$list_type>` 842 # up on the preceding line, to get it past the current stupid 843 # HTML block parser. This is a hack to work around the terrible 844 # hack that is the HTML block parser. 845 $result = rtrim($result); 846 $result = "<$list_type>" . $result . "</$list_type>\n"; 847 return $result; 848 } 849 function _DoLists_callback_nested($matches) { 850 # Re-usable patterns to match list item bullets and number markers: 851 $marker_ul = '[*+-]'; 852 $marker_ol = '\d+[.]'; 853 $marker_any = "(?:$marker_ul|$marker_ol)"; 854 855 $list = $matches[1]; 856 $list_type = preg_match("/$marker_ul/", $matches[3]) ? "ul" : "ol"; 857 858 $marker_any = ( $list_type == "ul" ? $marker_ul : $marker_ol ); 859 860 # Turn double returns into triple returns, so that we can make a 861 # paragraph for the last item in a list, if necessary: 862 $list = preg_replace("/\n{2,}/", "\n\n\n", $list); 863 $result = _ProcessListItems($list, $marker_any); 864 $result = "<$list_type>\n" . $result . "</$list_type>\n"; 865 return $result; 866 } 867 868 869 function _ProcessListItems($list_str, $marker_any) { 870 # 871 # Process the contents of a single ordered or unordered list, splitting it 872 # into individual list items. 873 # 874 global $md_list_level; 875 876 # The $md_list_level global keeps track of when we're inside a list. 877 # Each time we enter a list, we increment it; when we leave a list, 878 # we decrement. If it's zero, we're not in a list anymore. 879 # 880 # We do this because when we're not inside a list, we want to treat 881 # something like this: 882 # 883 # I recommend upgrading to version 884 # 8. Oops, now this line is treated 885 # as a sub-list. 886 # 887 # As a single paragraph, despite the fact that the second line starts 888 # with a digit-period-space sequence. 889 # 890 # Whereas when we're inside a list (or sub-list), that line will be 891 # treated as the start of a sub-list. What a kludge, huh? This is 892 # an aspect of Markdown's syntax that's hard to parse perfectly 893 # without resorting to mind-reading. Perhaps the solution is to 894 # change the syntax rules such that sub-lists must start with a 895 # starting cardinal number; e.g. "1." or "a.". 896 897 $md_list_level++; 898 899 # trim trailing blank lines: 900 $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str); 901 902 $list_str = preg_replace_callback('{ 903 (\n)? # leading line = $1 904 (^[ \t]*) # leading whitespace = $2 905 ('.$marker_any.') [ \t]+ # list marker = $3 906 ((?s:.+?) # list item text = $4 907 (\n{1,2})) 908 (?= \n* (\z | \2 ('.$marker_any.') [ \t]+)) 909 }xm', 910 '_ProcessListItems_callback', $list_str); 911 912 $md_list_level--; 913 return $list_str; 914 } 915 function _ProcessListItems_callback($matches) { 916 $item = $matches[4]; 917 $leading_line =& $matches[1]; 918 $leading_space =& $matches[2]; 919 920 if ($leading_line || preg_match('/\n{2,}/', $item)) { 921 $item = _RunBlockGamut(_Outdent($item)); 922 } 923 else { 924 # Recursion for sub-lists: 925 $item = _DoLists(_Outdent($item)); 926 $item = preg_replace('/\n+$/', '', $item); 927 $item = _RunSpanGamut($item); 928 } 929 930 return "<li>" . $item . "</li>\n"; 931 } 932 933 934 function _DoCodeBlocks($text) { 935 # 936 # Process Markdown `<pre><code>` blocks. 937 # 938 global $md_tab_width; 939 $text = preg_replace_callback('{ 940 (?:\n\n|\A) 941 ( # $1 = the code block -- one or more lines, starting with a space/tab 942 (?: 943 (?:[ ]{'.$md_tab_width.'} | \t) # Lines must start with a tab or a tab-width of spaces 944 .*\n+ 945 )+ 946 ) 947 ((?=^[ ]{0,'.$md_tab_width.'}\S)|\Z) # Lookahead for non-space at line-start, or end of doc 948 }xm', 949 '_DoCodeBlocks_callback', $text); 950 951 return $text; 952 } 953 function _DoCodeBlocks_callback($matches) { 954 $codeblock = $matches[1]; 955 956 $codeblock = _EncodeCode(_Outdent($codeblock)); 957 // $codeblock = _Detab($codeblock); 958 # trim leading newlines and trailing whitespace 959 $codeblock = preg_replace(array('/\A\n+/', '/\s+\z/'), '', $codeblock); 960 961 $result = "\n\n<pre><code>" . $codeblock . "\n</code></pre>\n\n"; 962 963 return $result; 964 } 965 966 967 function _DoCodeSpans($text) { 968 # 969 # * Backtick quotes are used for <code></code> spans. 970 # 971 # * You can use multiple backticks as the delimiters if you want to 972 # include literal backticks in the code span. So, this input: 973 # 974 # Just type ``foo `bar` baz`` at the prompt. 975 # 976 # Will translate to: 977 # 978 # <p>Just type <code>foo `bar` baz</code> at the prompt.</p> 979 # 980 # There's no arbitrary limit to the number of backticks you 981 # can use as delimters. If you need three consecutive backticks 982 # in your code, use four for delimiters, etc. 983 # 984 # * You can use spaces to get literal backticks at the edges: 985 # 986 # ... type `` `bar` `` ... 987 # 988 # Turns to: 989 # 990 # ... type <code>`bar`</code> ... 991 # 992 $text = preg_replace_callback('@ 993 (?<!\\\) # Character before opening ` can\'t be a backslash 994 (`+) # $1 = Opening run of ` 995 (.+?) # $2 = The code block 996 (?<!`) 997 \1 # Matching closer 998 (?!`) 999 @xs', 1000 '_DoCodeSpans_callback', $text); 1001 1002 return $text; 1003 } 1004 function _DoCodeSpans_callback($matches) { 1005 $c = $matches[2]; 1006 $c = preg_replace('/^[ \t]*/', '', $c); # leading whitespace 1007 $c = preg_replace('/[ \t]*$/', '', $c); # trailing whitespace 1008 $c = _EncodeCode($c); 1009 return "<code>$c</code>"; 1010 } 1011 1012 1013 function _EncodeCode($_) { 1014 # 1015 # Encode/escape certain characters inside Markdown code runs. 1016 # The point is that in code, these characters are literals, 1017 # and lose their special Markdown meanings. 1018 # 1019 global $md_escape_table; 1020 1021 # Encode all ampersands; HTML entities are not 1022 # entities within a Markdown code span. 1023 $_ = str_replace('&', '&', $_); 1024 1025 # Do the angle bracket song and dance: 1026 $_ = str_replace(array('<', '>'), 1027 array('<', '>'), $_); 1028 1029 # Now, escape characters that are magic in Markdown: 1030 $_ = str_replace(array_keys($md_escape_table), 1031 array_values($md_escape_table), $_); 1032 1033 return $_; 1034 } 1035 1036 1037 function _DoItalicsAndBold($text) { 1038 # <strong> must go first: 1039 $text = preg_replace('{ 1040 ( # $1: Marker 1041 (?<!\*\*) \*\* | # (not preceded by two chars of 1042 (?<!__) __ # the same marker) 1043 ) 1044 (?=\S) # Not followed by whitespace 1045 (?!\1) # or two others marker chars. 1046 ( # $2: Content 1047 (?: 1048 [^*_]+? # Anthing not em markers. 1049 | 1050 # Balence any regular emphasis inside. 1051 ([*_]) (?=\S) .+? (?<=\S) \3 # $3: em char (* or _) 1052 | 1053 (?! \1 ) . # Allow unbalenced * and _. 1054 )+? 1055 ) 1056 (?<=\S) \1 # End mark not preceded by whitespace. 1057 }sx', 1058 '<strong>\2</strong>', $text); 1059 # Then <em>: 1060 $text = preg_replace( 1061 '{ ( (?<!\*)\* | (?<!_)_ ) (?=\S) (?! \1) (.+?) (?<=\S) \1 }sx', 1062 '<em>\2</em>', $text); 1063 1064 return $text; 1065 } 1066 1067 1068 function _DoBlockQuotes($text) { 1069 $text = preg_replace_callback('/ 1070 ( # Wrap whole match in $1 1071 ( 1072 ^[ \t]*>[ \t]? # ">" at the start of a line 1073 .+\n # rest of the first line 1074 (.+\n)* # subsequent consecutive lines 1075 \n* # blanks 1076 )+ 1077 ) 1078 /xm', 1079 '_DoBlockQuotes_callback', $text); 1080 1081 return $text; 1082 } 1083 function _DoBlockQuotes_callback($matches) { 1084 $bq = $matches[1]; 1085 # trim one level of quoting - trim whitespace-only lines 1086 $bq = preg_replace(array('/^[ \t]*>[ \t]?/m', '/^[ \t]+$/m'), '', $bq); 1087 $bq = _RunBlockGamut($bq); # recurse 1088 1089 $bq = preg_replace('/^/m', " ", $bq); 1090 # These leading spaces screw with <pre> content, so we need to fix that: 1091 $bq = preg_replace_callback('{(\s*<pre>.+?</pre>)}sx', 1092 '_DoBlockQuotes_callback2', $bq); 1093 1094 return "<blockquote>\n$bq\n</blockquote>\n\n"; 1095 } 1096 function _DoBlockQuotes_callback2($matches) { 1097 $pre = $matches[1]; 1098 $pre = preg_replace('/^ /m', '', $pre); 1099 return $pre; 1100 } 1101 1102 1103 function _FormParagraphs($text) { 1104 # 1105 # Params: 1106 # $text - string to process with html <p> tags 1107 # 1108 global $md_html_blocks; 1109 1110 # Strip leading and trailing lines: 1111 $text = preg_replace(array('/\A\n+/', '/\n+\z/'), '', $text); 1112 1113 $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY); 1114 1115 # 1116 # Wrap <p> tags. 1117 # 1118 foreach ($grafs as $key => $value) { 1119 if (!isset( $md_html_blocks[$value] )) { 1120 $value = _RunSpanGamut($value); 1121 $value = preg_replace('/^([ \t]*)/', '<p>', $value); 1122 $value .= "</p>"; 1123 $grafs[$key] = $value; 1124 } 1125 } 1126 1127 # 1128 # Unhashify HTML blocks 1129 # 1130 foreach ($grafs as $key => $value) { 1131 if (isset( $md_html_blocks[$value] )) { 1132 $grafs[$key] = $md_html_blocks[$value]; 1133 } 1134 } 1135 1136 return implode("\n\n", $grafs); 1137 } 1138 1139 1140 function _EncodeAmpsAndAngles($text) { 1141 # Smart processing for ampersands and angle brackets that need to be encoded. 1142 1143 # Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin: 1144 # http://bumppo.net/projects/amputator/ 1145 $text = preg_replace('/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/', 1146 '&', $text);; 1147 1148 # Encode naked <'s 1149 $text = preg_replace('{<(?![a-z/?\$!])}i', '<', $text); 1150 1151 return $text; 1152 } 1153 1154 1155 function _EncodeBackslashEscapes($text) { 1156 # 1157 # Parameter: String. 1158 # Returns: The string, with after processing the following backslash 1159 # escape sequences. 1160 # 1161 global $md_escape_table, $md_backslash_escape_table; 1162 # Must process escaped backslashes first. 1163 return str_replace(array_keys($md_backslash_escape_table), 1164 array_values($md_backslash_escape_table), $text); 1165 } 1166 1167 1168 function _DoAutoLinks($text) { 1169 $text = preg_replace("!<((https?|ftp):[^'\">\\s]+)>!", 1170 '<a href="\1">\1</a>', $text); 1171 1172 # Email addresses: <address@domain.foo> 1173 $text = preg_replace('{ 1174 < 1175 (?:mailto:)? 1176 ( 1177 [-.\w]+ 1178 \@ 1179 [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+ 1180 ) 1181 > 1182 }exi', 1183 "_EncodeEmailAddress(_UnescapeSpecialChars(_UnslashQuotes('\\1')))", 1184 $text); 1185 1186 return $text; 1187 } 1188 1189 1190 function _EncodeEmailAddress($addr) { 1191 # 1192 # Input: an email address, e.g. "foo@example.com" 1193 # 1194 # Output: the email address as a mailto link, with each character 1195 # of the address encoded as either a decimal or hex entity, in 1196 # the hopes of foiling most address harvesting spam bots. E.g.: 1197 # 1198 # <a href="mailto:foo@e 1199 # xample.com">foo 1200 # @example.com</a> 1201 # 1202 # Based by a filter by Matthew Wickline, posted to the BBEdit-Talk 1203 # mailing list: <http://tinyurl.com/yu7ue> 1204 # 1205 $addr = "mailto:" . $addr; 1206 $length = strlen($addr); 1207 1208 # leave ':' alone (to spot mailto: later) 1209 $addr = preg_replace_callback('/([^\:])/', 1210 '_EncodeEmailAddress_callback', $addr); 1211 1212 $addr = "<a href=\"$addr\">$addr</a>"; 1213 # strip the mailto: from the visible part 1214 $addr = preg_replace('/">.+?:/', '">', $addr); 1215 1216 return $addr; 1217 } 1218 function _EncodeEmailAddress_callback($matches) { 1219 $char = $matches[1]; 1220 $r = rand(0, 100); 1221 # roughly 10% raw, 45% hex, 45% dec 1222 # '@' *must* be encoded. I insist. 1223 if ($r > 90 && $char != '@') return $char; 1224 if ($r < 45) return '&#x'.dechex(ord($char)).';'; 1225 return '&#'.ord($char).';'; 1226 } 1227 1228 1229 function _UnescapeSpecialChars($text) { 1230 # 1231 # Swap back in all the special characters we've hidden. 1232 # 1233 global $md_escape_table; 1234 return str_replace(array_values($md_escape_table), 1235 array_keys($md_escape_table), $text); 1236 } 1237 1238 1239 # _TokenizeHTML is shared between PHP Markdown and PHP SmartyPants. 1240 # We only define it if it is not already defined. 1241 if (!function_exists('_TokenizeHTML')) : 1242 function _TokenizeHTML($str) { 1243 # 1244 # Parameter: String containing HTML markup. 1245 # Returns: An array of the tokens comprising the input 1246 # string. Each token is either a tag (possibly with nested, 1247 # tags contained therein, such as <a href="<MTFoo>">, or a 1248 # run of text between tags. Each element of the array is a 1249 # two-element array; the first is either 'tag' or 'text'; 1250 # the second is the actual value. 1251 # 1252 # 1253 # Regular expression derived from the _tokenize() subroutine in 1254 # Brad Choate's MTRegex plugin. 1255 # <http://www.bradchoate.com/past/mtregex.php> 1256 # 1257 $index = 0; 1258 $tokens = array(); 1259 1260 $match = '(?s:<!(?:--.*?--\s*)+>)|'. # comment 1261 '(?s:<\?.*?\?>)|'. # processing instruction 1262 # regular tags 1263 '(?:<[/!$]?[-a-zA-Z0-9:]+\b(?>[^"\'>]+|"[^"]*"|\'[^\']*\')*>)'; 1264 1265 $parts = preg_split("{($match)}", $str, -1, PREG_SPLIT_DELIM_CAPTURE); 1266 1267 foreach ($parts as $part) { 1268 if (++$index % 2 && $part != '') 1269 $tokens[] = array('text', $part); 1270 else 1271 $tokens[] = array('tag', $part); 1272 } 1273 1274 return $tokens; 1275 } 1276 endif; 1277 1278 1279 function _Outdent($text) { 1280 # 1281 # Remove one level of line-leading tabs or spaces 1282 # 1283 global $md_tab_width; 1284 return preg_replace("/^(\\t|[ ]{1,$md_tab_width})/m", "", $text); 1285 } 1286 1287 1288 function _Detab($text) { 1289 # 1290 # Replace tabs with the appropriate amount of space. 1291 # 1292 global $md_tab_width; 1293 1294 # For each line we separate the line in blocks delemited by 1295 # tab characters. Then we reconstruct every line by adding the 1296 # appropriate number of space between each blocks. 1297 1298 $lines = explode("\n", $text); 1299 $text = ""; 1300 1301 foreach ($lines as $line) { 1302 # Split in blocks. 1303 $blocks = explode("\t", $line); 1304 # Add each blocks to the line. 1305 $line = $blocks[0]; 1306 unset($blocks[0]); # Do not add first block twice. 1307 foreach ($blocks as $block) { 1308 # Calculate amount of space, insert spaces, insert block. 1309 $amount = $md_tab_width - strlen($line) % $md_tab_width; 1310 $line .= str_repeat(" ", $amount) . $block; 1311 } 1312 $text .= "$line\n"; 1313 } 1314 return $text; 1315 } 1316 1317 1318 function _UnslashQuotes($text) { 1319 # 1320 # This function is useful to remove automaticaly slashed double quotes 1321 # when using preg_replace and evaluating an expression. 1322 # Parameter: String. 1323 # Returns: The string with any slash-double-quote (\") sequence replaced 1324 # by a single double quote. 1325 # 1326 return str_replace('\"', '"', $text); 1327 } 1328 1329 1330 /* 1331 1332 PHP Markdown 1333 ============ 1334 1335 Description 1336 ----------- 1337 1338 This is a PHP translation of the original Markdown formatter written in 1339 Perl by John Gruber. 1340 1341 Markdown is a text-to-HTML filter; it translates an easy-to-read / 1342 easy-to-write structured text format into HTML. Markdown's text format 1343 is most similar to that of plain text email, and supports features such 1344 as headers, *emphasis*, code blocks, blockquotes, and links. 1345 1346 Markdown's syntax is designed not as a generic markup language, but 1347 specifically to serve as a front-end to (X)HTML. You can use span-level 1348 HTML tags anywhere in a Markdown document, and you can use block level 1349 HTML tags (like <div> and <table> as well). 1350 1351 For more information about Markdown's syntax, see: 1352 1353 <http://daringfireball.net/projects/markdown/> 1354 1355 1356 Bugs 1357 ---- 1358 1359 To file bug reports please send email to: 1360 1361 <michel.fortin@michelf.com> 1362 1363 Please include with your report: (1) the example input; (2) the output you 1364 expected; (3) the output Markdown actually produced. 1365 1366 1367 Version History 1368 --------------- 1369 1370 See the readme file for detailed release notes for this version. 1371 1372 1.0.1c - 9 Dec 2005 1373 1374 1.0.1b - 6 Jun 2005 1375 1376 1.0.1a - 15 Apr 2005 1377 1378 1.0.1 - 16 Dec 2004 1379 1380 1.0 - 21 Aug 2004 1381 1382 1383 Author & Contributors 1384 --------------------- 1385 1386 Original Perl version by John Gruber 1387 <http://daringfireball.net/> 1388 1389 PHP port and other contributions by Michel Fortin 1390 <http://www.michelf.com/> 1391 1392 1393 Copyright and License 1394 --------------------- 1395 1396 Copyright (c) 2004-2005 Michel Fortin 1397 <http://www.michelf.com/> 1398 All rights reserved. 1399 1400 Copyright (c) 2003-2004 John Gruber 1401 <http://daringfireball.net/> 1402 All rights reserved. 1403 1404 Redistribution and use in source and binary forms, with or without 1405 modification, are permitted provided that the following conditions are 1406 met: 1407 1408 * Redistributions of source code must retain the above copyright notice, 1409 this list of conditions and the following disclaimer. 1410 1411 * Redistributions in binary form must reproduce the above copyright 1412 notice, this list of conditions and the following disclaimer in the 1413 documentation and/or other materials provided with the distribution. 1414 1415 * Neither the name "Markdown" nor the names of its contributors may 1416 be used to endorse or promote products derived from this software 1417 without specific prior written permission. 1418 1419 This software is provided by the copyright holders and contributors "as 1420 is" and any express or implied warranties, including, but not limited 1421 to, the implied warranties of merchantability and fitness for a 1422 particular purpose are disclaimed. In no event shall the copyright owner 1423 or contributors be liable for any direct, indirect, incidental, special, 1424 exemplary, or consequential damages (including, but not limited to, 1425 procurement of substitute goods or services; loss of use, data, or 1426 profits; or business interruption) however caused and on any theory of 1427 liability, whether in contract, strict liability, or tort (including 1428 negligence or otherwise) arising in any way out of the use of this 1429 software, even if advised of the possibility of such damage. 1430 1431 */ 1432 ?>
titre
Description
Corps
titre
Description
Corps
titre
Description
Corps
titre
Corps
Généré le : Thu Nov 29 12:22:27 2007 | par Balluche grâce à PHPXref 0.7 |
![]() |