├── plugins ├── index.html ├── helloworld │ └── helloworld.php └── cimarkdown │ ├── cimarkdown.php │ └── markdown.php ├── plugins.sql ├── README.md └── application └── libraries └── Plugins.php /plugins/index.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |Directory access is forbidden.
8 | 9 | 10 | -------------------------------------------------------------------------------- /plugins/helloworld/helloworld.php: -------------------------------------------------------------------------------- 1 |All plugins found in the plugins directory.
"; 546 | echo ""; 547 | print_r(self::$plugins_pool); 548 | echo ""; 549 | echo "
Activated plugins that have already been included and are usable.
"; 557 | echo ""; 558 | print_r(self::$plugins_active); 559 | echo ""; 560 | echo "
Action hooks that have been registered by the application and can be called via plugin files.
"; 568 | echo ""; 569 | print_r(self::$actions); 570 | echo ""; 571 | echo "
Hooks that have been called previously.
"; 579 | echo ""; 580 | print_r(self::$run_actions); 581 | echo ""; 582 | echo "
s around 259 | # "paragraphs" that are wrapped in non-block-level tags, such as anchors, 260 | # phrase emphasis, and spans. The list of tags we're looking for is 261 | # hard-coded: 262 | # 263 | # * List "a" is made of tags which can be both inline or block-level. 264 | # These will be treated block-level when the start tag is alone on 265 | # its line, otherwise they're not matched here and will be taken as 266 | # inline later. 267 | # * List "b" is made of tags which are always block-level; 268 | # 269 | $block_tags_a_re = 'ins|del'; 270 | $block_tags_b_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|'. 271 | 'script|noscript|form|fieldset|iframe|math'; 272 | 273 | # Regular expression for the content of a block tag. 274 | $nested_tags_level = 4; 275 | $attr = ' 276 | (?> # optional tag attributes 277 | \s # starts with whitespace 278 | (?> 279 | [^>"/]+ # text outside quotes 280 | | 281 | /+(?!>) # slash not followed by ">" 282 | | 283 | "[^"]*" # text inside double quotes (tolerate ">") 284 | | 285 | \'[^\']*\' # text inside single quotes (tolerate ">") 286 | )* 287 | )? 288 | '; 289 | $content = 290 | str_repeat(' 291 | (?> 292 | [^<]+ # content without tag 293 | | 294 | <\2 # nested opening tag 295 | '.$attr.' # attributes 296 | (?> 297 | /> 298 | | 299 | >', $nested_tags_level). # end of opening tag 300 | '.*?'. # last level nested tag content 301 | str_repeat(' 302 | \2\s*> # closing nested tag 303 | ) 304 | | 305 | <(?!/\2\s*> # other tags with a different name 306 | ) 307 | )*', 308 | $nested_tags_level); 309 | $content2 = str_replace('\2', '\3', $content); 310 | 311 | # First, look for nested blocks, e.g.: 312 | #
` blocks.
967 | #
968 | $text = preg_replace_callback('{
969 | (?:\n\n|\A\n?)
970 | ( # $1 = the code block -- one or more lines, starting with a space/tab
971 | (?>
972 | [ ]{'.$this->tab_width.'} # Lines must start with a tab or a tab-width of spaces
973 | .*\n+
974 | )+
975 | )
976 | ((?=^[ ]{0,'.$this->tab_width.'}\S)|\Z) # Lookahead for non-space at line-start, or end of doc
977 | }xm',
978 | array(&$this, '_doCodeBlocks_callback'), $text);
979 |
980 | return $text;
981 | }
982 | function _doCodeBlocks_callback($matches) {
983 | $codeblock = $matches[1];
984 |
985 | $codeblock = $this->outdent($codeblock);
986 | $codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
987 |
988 | # trim leading newlines and trailing newlines
989 | $codeblock = preg_replace('/\A\n+|\n+\z/', '', $codeblock);
990 |
991 | $codeblock = "$codeblock\n
";
992 | return "\n\n".$this->hashBlock($codeblock)."\n\n";
993 | }
994 |
995 |
996 | function makeCodeSpan($code) {
997 | #
998 | # Create a code span markup for $code. Called from handleSpanToken.
999 | #
1000 | $code = htmlspecialchars(trim($code), ENT_NOQUOTES);
1001 | return $this->hashPart("$code");
1002 | }
1003 |
1004 |
1005 | var $em_relist = array(
1006 | '' => '(?:(? '(?<=\S|^)(? '(?<=\S|^)(? '(?:(? '(?<=\S|^)(? '(?<=\S|^)(? '(?:(? '(?<=\S|^)(? '(?<=\S|^)(?em_relist as $em => $em_re) {
1028 | foreach ($this->strong_relist as $strong => $strong_re) {
1029 | # Construct list of allowed token expressions.
1030 | $token_relist = array();
1031 | if (isset($this->em_strong_relist["$em$strong"])) {
1032 | $token_relist[] = $this->em_strong_relist["$em$strong"];
1033 | }
1034 | $token_relist[] = $em_re;
1035 | $token_relist[] = $strong_re;
1036 |
1037 | # Construct master expression from list.
1038 | $token_re = '{('. implode('|', $token_relist) .')}';
1039 | $this->em_strong_prepared_relist["$em$strong"] = $token_re;
1040 | }
1041 | }
1042 | }
1043 |
1044 | function doItalicsAndBold($text) {
1045 | $token_stack = array('');
1046 | $text_stack = array('');
1047 | $em = '';
1048 | $strong = '';
1049 | $tree_char_em = false;
1050 |
1051 | while (1) {
1052 | #
1053 | # Get prepared regular expression for seraching emphasis tokens
1054 | # in current context.
1055 | #
1056 | $token_re = $this->em_strong_prepared_relist["$em$strong"];
1057 |
1058 | #
1059 | # Each loop iteration search for the next emphasis token.
1060 | # Each token is then passed to handleSpanToken.
1061 | #
1062 | $parts = preg_split($token_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE);
1063 | $text_stack[0] .= $parts[0];
1064 | $token =& $parts[1];
1065 | $text =& $parts[2];
1066 |
1067 | if (empty($token)) {
1068 | # Reached end of text span: empty stack without emitting.
1069 | # any more emphasis.
1070 | while ($token_stack[0]) {
1071 | $text_stack[1] .= array_shift($token_stack);
1072 | $text_stack[0] .= array_shift($text_stack);
1073 | }
1074 | break;
1075 | }
1076 |
1077 | $token_len = strlen($token);
1078 | if ($tree_char_em) {
1079 | # Reached closing marker while inside a three-char emphasis.
1080 | if ($token_len == 3) {
1081 | # Three-char closing marker, close em and strong.
1082 | array_shift($token_stack);
1083 | $span = array_shift($text_stack);
1084 | $span = $this->runSpanGamut($span);
1085 | $span = "$span";
1086 | $text_stack[0] .= $this->hashPart($span);
1087 | $em = '';
1088 | $strong = '';
1089 | } else {
1090 | # Other closing marker: close one em or strong and
1091 | # change current token state to match the other
1092 | $token_stack[0] = str_repeat($token{0}, 3-$token_len);
1093 | $tag = $token_len == 2 ? "strong" : "em";
1094 | $span = $text_stack[0];
1095 | $span = $this->runSpanGamut($span);
1096 | $span = "<$tag>$span$tag>";
1097 | $text_stack[0] = $this->hashPart($span);
1098 | $$tag = ''; # $$tag stands for $em or $strong
1099 | }
1100 | $tree_char_em = false;
1101 | } else if ($token_len == 3) {
1102 | if ($em) {
1103 | # Reached closing marker for both em and strong.
1104 | # Closing strong marker:
1105 | for ($i = 0; $i < 2; ++$i) {
1106 | $shifted_token = array_shift($token_stack);
1107 | $tag = strlen($shifted_token) == 2 ? "strong" : "em";
1108 | $span = array_shift($text_stack);
1109 | $span = $this->runSpanGamut($span);
1110 | $span = "<$tag>$span$tag>";
1111 | $text_stack[0] .= $this->hashPart($span);
1112 | $$tag = ''; # $$tag stands for $em or $strong
1113 | }
1114 | } else {
1115 | # Reached opening three-char emphasis marker. Push on token
1116 | # stack; will be handled by the special condition above.
1117 | $em = $token{0};
1118 | $strong = "$em$em";
1119 | array_unshift($token_stack, $token);
1120 | array_unshift($text_stack, '');
1121 | $tree_char_em = true;
1122 | }
1123 | } else if ($token_len == 2) {
1124 | if ($strong) {
1125 | # Unwind any dangling emphasis marker:
1126 | if (strlen($token_stack[0]) == 1) {
1127 | $text_stack[1] .= array_shift($token_stack);
1128 | $text_stack[0] .= array_shift($text_stack);
1129 | }
1130 | # Closing strong marker:
1131 | array_shift($token_stack);
1132 | $span = array_shift($text_stack);
1133 | $span = $this->runSpanGamut($span);
1134 | $span = "$span";
1135 | $text_stack[0] .= $this->hashPart($span);
1136 | $strong = '';
1137 | } else {
1138 | array_unshift($token_stack, $token);
1139 | array_unshift($text_stack, '');
1140 | $strong = $token;
1141 | }
1142 | } else {
1143 | # Here $token_len == 1
1144 | if ($em) {
1145 | if (strlen($token_stack[0]) == 1) {
1146 | # Closing emphasis marker:
1147 | array_shift($token_stack);
1148 | $span = array_shift($text_stack);
1149 | $span = $this->runSpanGamut($span);
1150 | $span = "$span";
1151 | $text_stack[0] .= $this->hashPart($span);
1152 | $em = '';
1153 | } else {
1154 | $text_stack[0] .= $token;
1155 | }
1156 | } else {
1157 | array_unshift($token_stack, $token);
1158 | array_unshift($text_stack, '');
1159 | $em = $token;
1160 | }
1161 | }
1162 | }
1163 | return $text_stack[0];
1164 | }
1165 |
1166 |
1167 | function doBlockQuotes($text) {
1168 | $text = preg_replace_callback('/
1169 | ( # Wrap whole match in $1
1170 | (?>
1171 | ^[ ]*>[ ]? # ">" at the start of a line
1172 | .+\n # rest of the first line
1173 | (.+\n)* # subsequent consecutive lines
1174 | \n* # blanks
1175 | )+
1176 | )
1177 | /xm',
1178 | array(&$this, '_doBlockQuotes_callback'), $text);
1179 |
1180 | return $text;
1181 | }
1182 | function _doBlockQuotes_callback($matches) {
1183 | $bq = $matches[1];
1184 | # trim one level of quoting - trim whitespace-only lines
1185 | $bq = preg_replace('/^[ ]*>[ ]?|^[ ]+$/m', '', $bq);
1186 | $bq = $this->runBlockGamut($bq); # recurse
1187 |
1188 | $bq = preg_replace('/^/m', " ", $bq);
1189 | # These leading spaces cause problem with content,
1190 | # so we need to fix that:
1191 | $bq = preg_replace_callback('{(\s*.+?
)}sx',
1192 | array(&$this, '_doBlockQuotes_callback2'), $bq);
1193 |
1194 | return "\n". $this->hashBlock("\n$bq\n
")."\n\n";
1195 | }
1196 | function _doBlockQuotes_callback2($matches) {
1197 | $pre = $matches[1];
1198 | $pre = preg_replace('/^ /m', '', $pre);
1199 | return $pre;
1200 | }
1201 |
1202 |
1203 | function formParagraphs($text) {
1204 | #
1205 | # Params:
1206 | # $text - string to process with html tags
1207 | #
1208 | # Strip leading and trailing lines:
1209 | $text = preg_replace('/\A\n+|\n+\z/', '', $text);
1210 |
1211 | $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY);
1212 |
1213 | #
1214 | # Wrap
tags and unhashify HTML blocks
1215 | #
1216 | foreach ($grafs as $key => $value) {
1217 | if (!preg_match('/^B\x1A[0-9]+B$/', $value)) {
1218 | # Is a paragraph.
1219 | $value = $this->runSpanGamut($value);
1220 | $value = preg_replace('/^([ ]*)/', "
", $value);
1221 | $value .= "
";
1222 | $grafs[$key] = $this->unhash($value);
1223 | }
1224 | else {
1225 | # Is a block.
1226 | # Modify elements of @grafs in-place...
1227 | $graf = $value;
1228 | $block = $this->html_hashes[$graf];
1229 | $graf = $block;
1230 | // if (preg_match('{
1231 | // \A
1232 | // ( # $1 = tag
1233 | // ]*
1235 | // \b
1236 | // markdown\s*=\s* ([\'"]) # $2 = attr quote char
1237 | // 1
1238 | // \2
1239 | // [^>]*
1240 | // >
1241 | // )
1242 | // ( # $3 = contents
1243 | // .*
1244 | // )
1245 | // () # $4 = closing tag
1246 | // \z
1247 | // }xs', $block, $matches))
1248 | // {
1249 | // list(, $div_open, , $div_content, $div_close) = $matches;
1250 | //
1251 | // # We can't call Markdown(), because that resets the hash;
1252 | // # that initialization code should be pulled into its own sub, though.
1253 | // $div_content = $this->hashHTMLBlocks($div_content);
1254 | //
1255 | // # Run document gamut methods on the content.
1256 | // foreach ($this->document_gamut as $method => $priority) {
1257 | // $div_content = $this->$method($div_content);
1258 | // }
1259 | //
1260 | // $div_open = preg_replace(
1261 | // '{\smarkdown\s*=\s*([\'"]).+?\1}', '', $div_open);
1262 | //
1263 | // $graf = $div_open . "\n" . $div_content . "\n" . $div_close;
1264 | // }
1265 | $grafs[$key] = $graf;
1266 | }
1267 | }
1268 |
1269 | return implode("\n\n", $grafs);
1270 | }
1271 |
1272 |
1273 | function encodeAttribute($text) {
1274 | #
1275 | # Encode text for a double-quoted HTML attribute. This function
1276 | # is *not* suitable for attributes enclosed in single quotes.
1277 | #
1278 | $text = $this->encodeAmpsAndAngles($text);
1279 | $text = str_replace('"', '"', $text);
1280 | return $text;
1281 | }
1282 |
1283 |
1284 | function encodeAmpsAndAngles($text) {
1285 | #
1286 | # Smart processing for ampersands and angle brackets that need to
1287 | # be encoded. Valid character entities are left alone unless the
1288 | # no-entities mode is set.
1289 | #
1290 | if ($this->no_entities) {
1291 | $text = str_replace('&', '&', $text);
1292 | } else {
1293 | # Ampersand-encoding based entirely on Nat Irons's Amputator
1294 | # MT plugin:
1295 | $text = preg_replace('/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/',
1296 | '&', $text);;
1297 | }
1298 | # Encode remaining <'s
1299 | $text = str_replace('<', '<', $text);
1300 |
1301 | return $text;
1302 | }
1303 |
1304 |
1305 | function doAutoLinks($text) {
1306 | $text = preg_replace_callback('{<((https?|ftp|dict):[^\'">\s]+)>}i',
1307 | array(&$this, '_doAutoLinks_url_callback'), $text);
1308 |
1309 | # Email addresses:
1310 | $text = preg_replace_callback('{
1311 | <
1312 | (?:mailto:)?
1313 | (
1314 | (?:
1315 | [-!#$%&\'*+/=?^_`.{|}~\w\x80-\xFF]+
1316 | |
1317 | ".*?"
1318 | )
1319 | \@
1320 | (?:
1321 | [-a-z0-9\x80-\xFF]+(\.[-a-z0-9\x80-\xFF]+)*\.[a-z]+
1322 | |
1323 | \[[\d.a-fA-F:]+\] # IPv4 & IPv6
1324 | )
1325 | )
1326 | >
1327 | }xi',
1328 | array(&$this, '_doAutoLinks_email_callback'), $text);
1329 |
1330 | return $text;
1331 | }
1332 | function _doAutoLinks_url_callback($matches) {
1333 | $url = $this->encodeAttribute($matches[1]);
1334 | $link = "$url";
1335 | return $this->hashPart($link);
1336 | }
1337 | function _doAutoLinks_email_callback($matches) {
1338 | $address = $matches[1];
1339 | $link = $this->encodeEmailAddress($address);
1340 | return $this->hashPart($link);
1341 | }
1342 |
1343 |
1344 | function encodeEmailAddress($addr) {
1345 | #
1346 | # Input: an email address, e.g. "foo@example.com"
1347 | #
1348 | # Output: the email address as a mailto link, with each character
1349 | # of the address encoded as either a decimal or hex entity, in
1350 | # the hopes of foiling most address harvesting spam bots. E.g.:
1351 | #
1352 | #
1356 | #
1357 | # Based by a filter by Matthew Wickline, posted to BBEdit-Talk.
1358 | # With some optimizations by Milian Wolff.
1359 | #
1360 | $addr = "mailto:" . $addr;
1361 | $chars = preg_split('/(? $char) {
1365 | $ord = ord($char);
1366 | # Ignore non-ascii chars.
1367 | if ($ord < 128) {
1368 | $r = ($seed * (1 + $key)) % 100; # Pseudo-random function.
1369 | # roughly 10% raw, 45% hex, 45% dec
1370 | # '@' *must* be encoded. I insist.
1371 | if ($r > 90 && $char != '@') /* do nothing */;
1372 | else if ($r < 45) $chars[$key] = ''.dechex($ord).';';
1373 | else $chars[$key] = ''.$ord.';';
1374 | }
1375 | }
1376 |
1377 | $addr = implode('', $chars);
1378 | $text = implode('', array_slice($chars, 7)); # text without `mailto:`
1379 | $addr = "$text";
1380 |
1381 | return $addr;
1382 | }
1383 |
1384 |
1385 | function parseSpan($str) {
1386 | #
1387 | # Take the string $str and parse it into tokens, hashing embeded HTML,
1388 | # escaped characters and handling code spans.
1389 | #
1390 | $output = '';
1391 |
1392 | $span_re = '{
1393 | (
1394 | \\\\'.$this->escape_chars_re.'
1395 | |
1396 | (?no_markup ? '' : '
1399 | |
1400 | # comment
1401 | |
1402 | <\?.*?\?> | <%.*?%> # processing instruction
1403 | |
1404 | <[/!$]?[-a-zA-Z0-9:_]+ # regular tags
1405 | (?>
1406 | \s
1407 | (?>[^"\'>]+|"[^"]*"|\'[^\']*\')*
1408 | )?
1409 | >
1410 | ').'
1411 | )
1412 | }xs';
1413 |
1414 | while (1) {
1415 | #
1416 | # Each loop iteration seach for either the next tag, the next
1417 | # openning code span marker, or the next escaped character.
1418 | # Each token is then passed to handleSpanToken.
1419 | #
1420 | $parts = preg_split($span_re, $str, 2, PREG_SPLIT_DELIM_CAPTURE);
1421 |
1422 | # Create token from text preceding tag.
1423 | if ($parts[0] != "") {
1424 | $output .= $parts[0];
1425 | }
1426 |
1427 | # Check if we reach the end.
1428 | if (isset($parts[1])) {
1429 | $output .= $this->handleSpanToken($parts[1], $parts[2]);
1430 | $str = $parts[2];
1431 | }
1432 | else {
1433 | break;
1434 | }
1435 | }
1436 |
1437 | return $output;
1438 | }
1439 |
1440 |
1441 | function handleSpanToken($token, &$str) {
1442 | #
1443 | # Handle $token provided by parseSpan by determining its nature and
1444 | # returning the corresponding value that should replace it.
1445 | #
1446 | switch ($token{0}) {
1447 | case "\\":
1448 | return $this->hashPart("". ord($token{1}). ";");
1449 | case "`":
1450 | # Search for end marker in remaining text.
1451 | if (preg_match('/^(.*?[^`])'.preg_quote($token).'(?!`)(.*)$/sm',
1452 | $str, $matches))
1453 | {
1454 | $str = $matches[2];
1455 | $codespan = $this->makeCodeSpan($matches[1]);
1456 | return $this->hashPart($codespan);
1457 | }
1458 | return $token; // return as text since no ending marker found.
1459 | default:
1460 | return $this->hashPart($token);
1461 | }
1462 | }
1463 |
1464 |
1465 | function outdent($text) {
1466 | #
1467 | # Remove one level of line-leading tabs or spaces
1468 | #
1469 | return preg_replace('/^(\t|[ ]{1,'.$this->tab_width.'})/m', '', $text);
1470 | }
1471 |
1472 |
1473 | # String length function for detab. `_initDetab` will create a function to
1474 | # hanlde UTF-8 if the default function does not exist.
1475 | var $utf8_strlen = 'mb_strlen';
1476 |
1477 | function detab($text) {
1478 | #
1479 | # Replace tabs with the appropriate amount of space.
1480 | #
1481 | # For each line we separate the line in blocks delemited by
1482 | # tab characters. Then we reconstruct every line by adding the
1483 | # appropriate number of space between each blocks.
1484 |
1485 | $text = preg_replace_callback('/^.*\t.*$/m',
1486 | array(&$this, '_detab_callback'), $text);
1487 |
1488 | return $text;
1489 | }
1490 | function _detab_callback($matches) {
1491 | $line = $matches[0];
1492 | $strlen = $this->utf8_strlen; # strlen function for UTF-8.
1493 |
1494 | # Split in blocks.
1495 | $blocks = explode("\t", $line);
1496 | # Add each blocks to the line.
1497 | $line = $blocks[0];
1498 | unset($blocks[0]); # Do not add first block twice.
1499 | foreach ($blocks as $block) {
1500 | # Calculate amount of space, insert spaces, insert block.
1501 | $amount = $this->tab_width -
1502 | $strlen($line, 'UTF-8') % $this->tab_width;
1503 | $line .= str_repeat(" ", $amount) . $block;
1504 | }
1505 | return $line;
1506 | }
1507 | function _initDetab() {
1508 | #
1509 | # Check for the availability of the function in the `utf8_strlen` property
1510 | # (initially `mb_strlen`). If the function is not available, create a
1511 | # function that will loosely count the number of UTF-8 characters with a
1512 | # regular expression.
1513 | #
1514 | if (function_exists($this->utf8_strlen)) return;
1515 | $this->utf8_strlen = create_function('$text', 'return preg_match_all(
1516 | "/[\\\\x00-\\\\xBF]|[\\\\xC0-\\\\xFF][\\\\x80-\\\\xBF]*/",
1517 | $text, $m);');
1518 | }
1519 |
1520 |
1521 | function unhash($text) {
1522 | #
1523 | # Swap back in all the tags hashed by _HashHTMLBlocks.
1524 | #
1525 | return preg_replace_callback('/(.)\x1A[0-9]+\1/',
1526 | array(&$this, '_unhash_callback'), $text);
1527 | }
1528 | function _unhash_callback($matches) {
1529 | return $this->html_hashes[$matches[0]];
1530 | }
1531 |
1532 | }
1533 |
1534 |
1535 | #
1536 | # Markdown Extra Parser Class
1537 | #
1538 |
1539 | class MarkdownExtra_Parser extends Markdown_Parser {
1540 |
1541 | # Prefix for footnote ids.
1542 | var $fn_id_prefix = "";
1543 |
1544 | # Optional title attribute for footnote links and backlinks.
1545 | var $fn_link_title = MARKDOWN_FN_LINK_TITLE;
1546 | var $fn_backlink_title = MARKDOWN_FN_BACKLINK_TITLE;
1547 |
1548 | # Optional class attribute for footnote links and backlinks.
1549 | var $fn_link_class = MARKDOWN_FN_LINK_CLASS;
1550 | var $fn_backlink_class = MARKDOWN_FN_BACKLINK_CLASS;
1551 |
1552 | # Predefined abbreviations.
1553 | var $predef_abbr = array();
1554 |
1555 |
1556 | function MarkdownExtra_Parser() {
1557 | #
1558 | # Constructor function. Initialize the parser object.
1559 | #
1560 | # Add extra escapable characters before parent constructor
1561 | # initialize the table.
1562 | $this->escape_chars .= ':|';
1563 |
1564 | # Insert extra document, block, and span transformations.
1565 | # Parent constructor will do the sorting.
1566 | $this->document_gamut += array(
1567 | "doFencedCodeBlocks" => 5,
1568 | "stripFootnotes" => 15,
1569 | "stripAbbreviations" => 25,
1570 | "appendFootnotes" => 50,
1571 | );
1572 | $this->block_gamut += array(
1573 | "doFencedCodeBlocks" => 5,
1574 | "doTables" => 15,
1575 | "doDefLists" => 45,
1576 | );
1577 | $this->span_gamut += array(
1578 | "doFootnotes" => 5,
1579 | "doAbbreviations" => 70,
1580 | );
1581 |
1582 | parent::Markdown_Parser();
1583 | }
1584 |
1585 |
1586 | # Extra variables used during extra transformations.
1587 | var $footnotes = array();
1588 | var $footnotes_ordered = array();
1589 | var $abbr_desciptions = array();
1590 | var $abbr_word_re = '';
1591 |
1592 | # Give the current footnote number.
1593 | var $footnote_counter = 1;
1594 |
1595 |
1596 | function setup() {
1597 | #
1598 | # Setting up Extra-specific variables.
1599 | #
1600 | parent::setup();
1601 |
1602 | $this->footnotes = array();
1603 | $this->footnotes_ordered = array();
1604 | $this->abbr_desciptions = array();
1605 | $this->abbr_word_re = '';
1606 | $this->footnote_counter = 1;
1607 |
1608 | foreach ($this->predef_abbr as $abbr_word => $abbr_desc) {
1609 | if ($this->abbr_word_re)
1610 | $this->abbr_word_re .= '|';
1611 | $this->abbr_word_re .= preg_quote($abbr_word);
1612 | $this->abbr_desciptions[$abbr_word] = trim($abbr_desc);
1613 | }
1614 | }
1615 |
1616 | function teardown() {
1617 | #
1618 | # Clearing Extra-specific variables.
1619 | #
1620 | $this->footnotes = array();
1621 | $this->footnotes_ordered = array();
1622 | $this->abbr_desciptions = array();
1623 | $this->abbr_word_re = '';
1624 |
1625 | parent::teardown();
1626 | }
1627 |
1628 |
1629 | ### HTML Block Parser ###
1630 |
1631 | # Tags that are always treated as block tags:
1632 | var $block_tags_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|form|fieldset|iframe|hr|legend';
1633 |
1634 | # Tags treated as block tags only if the opening tag is alone on it's line:
1635 | var $context_block_tags_re = 'script|noscript|math|ins|del';
1636 |
1637 | # Tags where markdown="1" default to span mode:
1638 | var $contain_span_tags_re = 'p|h[1-6]|li|dd|dt|td|th|legend|address';
1639 |
1640 | # Tags which must not have their contents modified, no matter where
1641 | # they appear:
1642 | var $clean_tags_re = 'script|math';
1643 |
1644 | # Tags that do not need to be closed.
1645 | var $auto_close_tags_re = 'hr|img';
1646 |
1647 |
1648 | function hashHTMLBlocks($text) {
1649 | #
1650 | # Hashify HTML Blocks and "clean tags".
1651 | #
1652 | # We only want to do this for block-level HTML tags, such as headers,
1653 | # lists, and tables. That's because we still want to wrap s around
1654 | # "paragraphs" that are wrapped in non-block-level tags, such as anchors,
1655 | # phrase emphasis, and spans. The list of tags we're looking for is
1656 | # hard-coded.
1657 | #
1658 | # This works by calling _HashHTMLBlocks_InMarkdown, which then calls
1659 | # _HashHTMLBlocks_InHTML when it encounter block tags. When the markdown="1"
1660 | # attribute is found whitin a tag, _HashHTMLBlocks_InHTML calls back
1661 | # _HashHTMLBlocks_InMarkdown to handle the Markdown syntax within the tag.
1662 | # These two functions are calling each other. It's recursive!
1663 | #
1664 | #
1665 | # Call the HTML-in-Markdown hasher.
1666 | #
1667 | list($text, ) = $this->_hashHTMLBlocks_inMarkdown($text);
1668 |
1669 | return $text;
1670 | }
1671 | function _hashHTMLBlocks_inMarkdown($text, $indent = 0,
1672 | $enclosing_tag_re = '', $span = false)
1673 | {
1674 | #
1675 | # Parse markdown text, calling _HashHTMLBlocks_InHTML for block tags.
1676 | #
1677 | # * $indent is the number of space to be ignored when checking for code
1678 | # blocks. This is important because if we don't take the indent into
1679 | # account, something like this (which looks right) won't work as expected:
1680 | #
1681 | #
1682 | #
1683 | # Hello World. <-- Is this a Markdown code block or text?
1684 | # <-- Is this a Markdown code block or a real tag?
1685 | #
1686 | #
1687 | # If you don't like this, just don't indent the tag on which
1688 | # you apply the markdown="1" attribute.
1689 | #
1690 | # * If $enclosing_tag_re is not empty, stops at the first unmatched closing
1691 | # tag with that name. Nested tags supported.
1692 | #
1693 | # * If $span is true, text inside must treated as span. So any double
1694 | # newline will be replaced by a single newline so that it does not create
1695 | # paragraphs.
1696 | #
1697 | # Returns an array of that form: ( processed text , remaining text )
1698 | #
1699 | if ($text === '') return array('', '');
1700 |
1701 | # Regex to check for the presense of newlines around a block tag.
1702 | $newline_before_re = '/(?:^\n?|\n\n)*$/';
1703 | $newline_after_re =
1704 | '{
1705 | ^ # Start of text following the tag.
1706 | (?>[ ]*)? # Optional comment.
1707 | [ ]*\n # Must be followed by newline.
1708 | }xs';
1709 |
1710 | # Regex to match any tag.
1711 | $block_tag_re =
1712 | '{
1713 | ( # $2: Capture hole tag.
1714 | ? # Any opening or closing tag.
1715 | (?> # Tag name.
1716 | '.$this->block_tags_re.' |
1717 | '.$this->context_block_tags_re.' |
1718 | '.$this->clean_tags_re.' |
1719 | (?!\s)'.$enclosing_tag_re.'
1720 | )
1721 | (?:
1722 | (?=[\s"\'/a-zA-Z0-9]) # Allowed characters after tag name.
1723 | (?>
1724 | ".*?" | # Double quotes (can contain `>`)
1725 | \'.*?\' | # Single quotes (can contain `>`)
1726 | .+? # Anything but quotes and `>`.
1727 | )*?
1728 | )?
1729 | > # End of tag.
1730 | |
1731 | # HTML Comment
1732 | |
1733 | <\?.*?\?> | <%.*?%> # Processing instruction
1734 | |
1735 | # CData Block
1736 | |
1737 | # Code span marker
1738 | `+
1739 | '. ( !$span ? ' # If not in span.
1740 | |
1741 | # Indented code block
1742 | (?: ^[ ]*\n | ^ | \n[ ]*\n )
1743 | [ ]{'.($indent+4).'}[^\n]* \n
1744 | (?>
1745 | (?: [ ]{'.($indent+4).'}[^\n]* | [ ]* ) \n
1746 | )*
1747 | |
1748 | # Fenced code block marker
1749 | (?> ^ | \n )
1750 | [ ]{'.($indent).'}~~~+[ ]*\n
1751 | ' : '' ). ' # End (if not is span).
1752 | )
1753 | }xs';
1754 |
1755 |
1756 | $depth = 0; # Current depth inside the tag tree.
1757 | $parsed = ""; # Parsed text that will be returned.
1758 |
1759 | #
1760 | # Loop through every tag until we find the closing tag of the parent
1761 | # or loop until reaching the end of text if no parent tag specified.
1762 | #
1763 | do {
1764 | #
1765 | # Split the text using the first $tag_match pattern found.
1766 | # Text before pattern will be first in the array, text after
1767 | # pattern will be at the end, and between will be any catches made
1768 | # by the pattern.
1769 | #
1770 | $parts = preg_split($block_tag_re, $text, 2,
1771 | PREG_SPLIT_DELIM_CAPTURE);
1772 |
1773 | # If in Markdown span mode, add a empty-string span-level hash
1774 | # after each newline to prevent triggering any block element.
1775 | if ($span) {
1776 | $void = $this->hashPart("", ':');
1777 | $newline = "$void\n";
1778 | $parts[0] = $void . str_replace("\n", $newline, $parts[0]) . $void;
1779 | }
1780 |
1781 | $parsed .= $parts[0]; # Text before current tag.
1782 |
1783 | # If end of $text has been reached. Stop loop.
1784 | if (count($parts) < 3) {
1785 | $text = "";
1786 | break;
1787 | }
1788 |
1789 | $tag = $parts[1]; # Tag to handle.
1790 | $text = $parts[2]; # Remaining text after current tag.
1791 | $tag_re = preg_quote($tag); # For use in a regular expression.
1792 |
1793 | #
1794 | # Check for: Code span marker
1795 | #
1796 | if ($tag{0} == "`") {
1797 | # Find corresponding end marker.
1798 | $tag_re = preg_quote($tag);
1799 | if (preg_match('{^(?>.+?|\n(?!\n))*?(?.*\n)+?'.$tag_re.' *\n}', $text,
1826 | $matches))
1827 | {
1828 | # End marker found: pass text unchanged until marker.
1829 | $parsed .= $tag . $matches[0];
1830 | $text = substr($text, strlen($matches[0]));
1831 | }
1832 | else {
1833 | # No end marker: just skip it.
1834 | $parsed .= $tag;
1835 | }
1836 | }
1837 | #
1838 | # Check for: Opening Block level tag or
1839 | # Opening Context Block tag (like ins and del)
1840 | # used as a block tag (tag is alone on it's line).
1841 | #
1842 | else if (preg_match('{^<(?:'.$this->block_tags_re.')\b}', $tag) ||
1843 | ( preg_match('{^<(?:'.$this->context_block_tags_re.')\b}', $tag) &&
1844 | preg_match($newline_before_re, $parsed) &&
1845 | preg_match($newline_after_re, $text) )
1846 | )
1847 | {
1848 | # Need to parse tag and following text using the HTML parser.
1849 | list($block_text, $text) =
1850 | $this->_hashHTMLBlocks_inHTML($tag . $text, "hashBlock", true);
1851 |
1852 | # Make sure it stays outside of any paragraph by adding newlines.
1853 | $parsed .= "\n\n$block_text\n\n";
1854 | }
1855 | #
1856 | # Check for: Clean tag (like script, math)
1857 | # HTML Comments, processing instructions.
1858 | #
1859 | else if (preg_match('{^<(?:'.$this->clean_tags_re.')\b}', $tag) ||
1860 | $tag{1} == '!' || $tag{1} == '?')
1861 | {
1862 | # Need to parse tag and following text using the HTML parser.
1863 | # (don't check for markdown attribute)
1864 | list($block_text, $text) =
1865 | $this->_hashHTMLBlocks_inHTML($tag . $text, "hashClean", false);
1866 |
1867 | $parsed .= $block_text;
1868 | }
1869 | #
1870 | # Check for: Tag with same name as enclosing tag.
1871 | #
1872 | else if ($enclosing_tag_re !== '' &&
1873 | # Same name as enclosing tag.
1874 | preg_match('{^?(?:'.$enclosing_tag_re.')\b}', $tag))
1875 | {
1876 | #
1877 | # Increase/decrease nested tag count.
1878 | #
1879 | if ($tag{1} == '/') $depth--;
1880 | else if ($tag{strlen($tag)-2} != '/') $depth++;
1881 |
1882 | if ($depth < 0) {
1883 | #
1884 | # Going out of parent element. Clean up and break so we
1885 | # return to the calling function.
1886 | #
1887 | $text = $tag . $text;
1888 | break;
1889 | }
1890 |
1891 | $parsed .= $tag;
1892 | }
1893 | else {
1894 | $parsed .= $tag;
1895 | }
1896 | } while ($depth >= 0);
1897 |
1898 | return array($parsed, $text);
1899 | }
1900 | function _hashHTMLBlocks_inHTML($text, $hash_method, $md_attr) {
1901 | #
1902 | # Parse HTML, calling _HashHTMLBlocks_InMarkdown for block tags.
1903 | #
1904 | # * Calls $hash_method to convert any blocks.
1905 | # * Stops when the first opening tag closes.
1906 | # * $md_attr indicate if the use of the `markdown="1"` attribute is allowed.
1907 | # (it is not inside clean tags)
1908 | #
1909 | # Returns an array of that form: ( processed text , remaining text )
1910 | #
1911 | if ($text === '') return array('', '');
1912 |
1913 | # Regex to match `markdown` attribute inside of a tag.
1914 | $markdown_attr_re = '
1915 | {
1916 | \s* # Eat whitespace before the `markdown` attribute
1917 | markdown
1918 | \s*=\s*
1919 | (?>
1920 | (["\']) # $1: quote delimiter
1921 | (.*?) # $2: attribute value
1922 | \1 # matching delimiter
1923 | |
1924 | ([^\s>]*) # $3: unquoted attribute value
1925 | )
1926 | () # $4: make $3 always defined (avoid warnings)
1927 | }xs';
1928 |
1929 | # Regex to match any tag.
1930 | $tag_re = '{
1931 | ( # $2: Capture hole tag.
1932 | ? # Any opening or closing tag.
1933 | [\w:$]+ # Tag name.
1934 | (?:
1935 | (?=[\s"\'/a-zA-Z0-9]) # Allowed characters after tag name.
1936 | (?>
1937 | ".*?" | # Double quotes (can contain `>`)
1938 | \'.*?\' | # Single quotes (can contain `>`)
1939 | .+? # Anything but quotes and `>`.
1940 | )*?
1941 | )?
1942 | > # End of tag.
1943 | |
1944 | # HTML Comment
1945 | |
1946 | <\?.*?\?> | <%.*?%> # Processing instruction
1947 | |
1948 | # CData Block
1949 | )
1950 | }xs';
1951 |
1952 | $original_text = $text; # Save original text in case of faliure.
1953 |
1954 | $depth = 0; # Current depth inside the tag tree.
1955 | $block_text = ""; # Temporary text holder for current text.
1956 | $parsed = ""; # Parsed text that will be returned.
1957 |
1958 | #
1959 | # Get the name of the starting tag.
1960 | # (This pattern makes $base_tag_name_re safe without quoting.)
1961 | #
1962 | if (preg_match('/^<([\w:$]*)\b/', $text, $matches))
1963 | $base_tag_name_re = $matches[1];
1964 |
1965 | #
1966 | # Loop through every tag until we find the corresponding closing tag.
1967 | #
1968 | do {
1969 | #
1970 | # Split the text using the first $tag_match pattern found.
1971 | # Text before pattern will be first in the array, text after
1972 | # pattern will be at the end, and between will be any catches made
1973 | # by the pattern.
1974 | #
1975 | $parts = preg_split($tag_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE);
1976 |
1977 | if (count($parts) < 3) {
1978 | #
1979 | # End of $text reached with unbalenced tag(s).
1980 | # In that case, we return original text unchanged and pass the
1981 | # first character as filtered to prevent an infinite loop in the
1982 | # parent function.
1983 | #
1984 | return array($original_text{0}, substr($original_text, 1));
1985 | }
1986 |
1987 | $block_text .= $parts[0]; # Text before current tag.
1988 | $tag = $parts[1]; # Tag to handle.
1989 | $text = $parts[2]; # Remaining text after current tag.
1990 |
1991 | #
1992 | # Check for: Auto-close tag (like
)
1993 | # Comments and Processing Instructions.
1994 | #
1995 | if (preg_match('{^?(?:'.$this->auto_close_tags_re.')\b}', $tag) ||
1996 | $tag{1} == '!' || $tag{1} == '?')
1997 | {
1998 | # Just add the tag to the block as if it was text.
1999 | $block_text .= $tag;
2000 | }
2001 | else {
2002 | #
2003 | # Increase/decrease nested tag count. Only do so if
2004 | # the tag's name match base tag's.
2005 | #
2006 | if (preg_match('{^?'.$base_tag_name_re.'\b}', $tag)) {
2007 | if ($tag{1} == '/') $depth--;
2008 | else if ($tag{strlen($tag)-2} != '/') $depth++;
2009 | }
2010 |
2011 | #
2012 | # Check for `markdown="1"` attribute and handle it.
2013 | #
2014 | if ($md_attr &&
2015 | preg_match($markdown_attr_re, $tag, $attr_m) &&
2016 | preg_match('/^1|block|span$/', $attr_m[2] . $attr_m[3]))
2017 | {
2018 | # Remove `markdown` attribute from opening tag.
2019 | $tag = preg_replace($markdown_attr_re, '', $tag);
2020 |
2021 | # Check if text inside this tag must be parsed in span mode.
2022 | $this->mode = $attr_m[2] . $attr_m[3];
2023 | $span_mode = $this->mode == 'span' || $this->mode != 'block' &&
2024 | preg_match('{^<(?:'.$this->contain_span_tags_re.')\b}', $tag);
2025 |
2026 | # Calculate indent before tag.
2027 | if (preg_match('/(?:^|\n)( *?)(?! ).*?$/', $block_text, $matches)) {
2028 | $strlen = $this->utf8_strlen;
2029 | $indent = $strlen($matches[1], 'UTF-8');
2030 | } else {
2031 | $indent = 0;
2032 | }
2033 |
2034 | # End preceding block with this tag.
2035 | $block_text .= $tag;
2036 | $parsed .= $this->$hash_method($block_text);
2037 |
2038 | # Get enclosing tag name for the ParseMarkdown function.
2039 | # (This pattern makes $tag_name_re safe without quoting.)
2040 | preg_match('/^<([\w:$]*)\b/', $tag, $matches);
2041 | $tag_name_re = $matches[1];
2042 |
2043 | # Parse the content using the HTML-in-Markdown parser.
2044 | list ($block_text, $text)
2045 | = $this->_hashHTMLBlocks_inMarkdown($text, $indent,
2046 | $tag_name_re, $span_mode);
2047 |
2048 | # Outdent markdown text.
2049 | if ($indent > 0) {
2050 | $block_text = preg_replace("/^[ ]{1,$indent}/m", "",
2051 | $block_text);
2052 | }
2053 |
2054 | # Append tag content to parsed text.
2055 | if (!$span_mode) $parsed .= "\n\n$block_text\n\n";
2056 | else $parsed .= "$block_text";
2057 |
2058 | # Start over a new block.
2059 | $block_text = "";
2060 | }
2061 | else $block_text .= $tag;
2062 | }
2063 |
2064 | } while ($depth > 0);
2065 |
2066 | #
2067 | # Hash last block text that wasn't processed inside the loop.
2068 | #
2069 | $parsed .= $this->$hash_method($block_text);
2070 |
2071 | return array($parsed, $text);
2072 | }
2073 |
2074 |
2075 | function hashClean($text) {
2076 | #
2077 | # Called whenever a tag must be hashed when a function insert a "clean" tag
2078 | # in $text, it pass through this function and is automaticaly escaped,
2079 | # blocking invalid nested overlap.
2080 | #
2081 | return $this->hashPart($text, 'C');
2082 | }
2083 |
2084 |
2085 | function doHeaders($text) {
2086 | #
2087 | # Redefined to add id attribute support.
2088 | #
2089 | # Setext-style headers:
2090 | # Header 1 {#header1}
2091 | # ========
2092 | #
2093 | # Header 2 {#header2}
2094 | # --------
2095 | #
2096 | $text = preg_replace_callback(
2097 | '{
2098 | (^.+?) # $1: Header text
2099 | (?:[ ]+\{\#([-_:a-zA-Z0-9]+)\})? # $2: Id attribute
2100 | [ ]*\n(=+|-+)[ ]*\n+ # $3: Header footer
2101 | }mx',
2102 | array(&$this, '_doHeaders_callback_setext'), $text);
2103 |
2104 | # atx-style headers:
2105 | # # Header 1 {#header1}
2106 | # ## Header 2 {#header2}
2107 | # ## Header 2 with closing hashes ## {#header3}
2108 | # ...
2109 | # ###### Header 6 {#header2}
2110 | #
2111 | $text = preg_replace_callback('{
2112 | ^(\#{1,6}) # $1 = string of #\'s
2113 | [ ]*
2114 | (.+?) # $2 = Header text
2115 | [ ]*
2116 | \#* # optional closing #\'s (not counted)
2117 | (?:[ ]+\{\#([-_:a-zA-Z0-9]+)\})? # id attribute
2118 | [ ]*
2119 | \n+
2120 | }xm',
2121 | array(&$this, '_doHeaders_callback_atx'), $text);
2122 |
2123 | return $text;
2124 | }
2125 | function _doHeaders_attr($attr) {
2126 | if (empty($attr)) return "";
2127 | return " id=\"$attr\"";
2128 | }
2129 | function _doHeaders_callback_setext($matches) {
2130 | if ($matches[3] == '-' && preg_match('{^- }', $matches[1]))
2131 | return $matches[0];
2132 | $level = $matches[3]{0} == '=' ? 1 : 2;
2133 | $attr = $this->_doHeaders_attr($id =& $matches[2]);
2134 | $block = "".$this->runSpanGamut($matches[1])." ";
2135 | return "\n" . $this->hashBlock($block) . "\n\n";
2136 | }
2137 | function _doHeaders_callback_atx($matches) {
2138 | $level = strlen($matches[1]);
2139 | $attr = $this->_doHeaders_attr($id =& $matches[3]);
2140 | $block = "".$this->runSpanGamut($matches[2])." ";
2141 | return "\n" . $this->hashBlock($block) . "\n\n";
2142 | }
2143 |
2144 |
2145 | function doTables($text) {
2146 | #
2147 | # Form HTML tables.
2148 | #
2149 | $less_than_tab = $this->tab_width - 1;
2150 | #
2151 | # Find tables with leading pipe.
2152 | #
2153 | # | Header 1 | Header 2
2154 | # | -------- | --------
2155 | # | Cell 1 | Cell 2
2156 | # | Cell 3 | Cell 4
2157 | #
2158 | $text = preg_replace_callback('
2159 | {
2160 | ^ # Start of a line
2161 | [ ]{0,'.$less_than_tab.'} # Allowed whitespace.
2162 | [|] # Optional leading pipe (present)
2163 | (.+) \n # $1: Header row (at least one pipe)
2164 |
2165 | [ ]{0,'.$less_than_tab.'} # Allowed whitespace.
2166 | [|] ([ ]*[-:]+[-| :]*) \n # $2: Header underline
2167 |
2168 | ( # $3: Cells
2169 | (?>
2170 | [ ]* # Allowed whitespace.
2171 | [|] .* \n # Row content.
2172 | )*
2173 | )
2174 | (?=\n|\Z) # Stop at final double newline.
2175 | }xm',
2176 | array(&$this, '_doTable_leadingPipe_callback'), $text);
2177 |
2178 | #
2179 | # Find tables without leading pipe.
2180 | #
2181 | # Header 1 | Header 2
2182 | # -------- | --------
2183 | # Cell 1 | Cell 2
2184 | # Cell 3 | Cell 4
2185 | #
2186 | $text = preg_replace_callback('
2187 | {
2188 | ^ # Start of a line
2189 | [ ]{0,'.$less_than_tab.'} # Allowed whitespace.
2190 | (\S.*[|].*) \n # $1: Header row (at least one pipe)
2191 |
2192 | [ ]{0,'.$less_than_tab.'} # Allowed whitespace.
2193 | ([-:]+[ ]*[|][-| :]*) \n # $2: Header underline
2194 |
2195 | ( # $3: Cells
2196 | (?>
2197 | .* [|] .* \n # Row content
2198 | )*
2199 | )
2200 | (?=\n|\Z) # Stop at final double newline.
2201 | }xm',
2202 | array(&$this, '_DoTable_callback'), $text);
2203 |
2204 | return $text;
2205 | }
2206 | function _doTable_leadingPipe_callback($matches) {
2207 | $head = $matches[1];
2208 | $underline = $matches[2];
2209 | $content = $matches[3];
2210 |
2211 | # Remove leading pipe for each row.
2212 | $content = preg_replace('/^ *[|]/m', '', $content);
2213 |
2214 | return $this->_doTable_callback(array($matches[0], $head, $underline, $content));
2215 | }
2216 | function _doTable_callback($matches) {
2217 | $head = $matches[1];
2218 | $underline = $matches[2];
2219 | $content = $matches[3];
2220 |
2221 | # Remove any tailing pipes for each line.
2222 | $head = preg_replace('/[|] *$/m', '', $head);
2223 | $underline = preg_replace('/[|] *$/m', '', $underline);
2224 | $content = preg_replace('/[|] *$/m', '', $content);
2225 |
2226 | # Reading alignement from header underline.
2227 | $separators = preg_split('/ *[|] */', $underline);
2228 | foreach ($separators as $n => $s) {
2229 | if (preg_match('/^ *-+: *$/', $s)) $attr[$n] = ' align="right"';
2230 | else if (preg_match('/^ *:-+: *$/', $s))$attr[$n] = ' align="center"';
2231 | else if (preg_match('/^ *:-+ *$/', $s)) $attr[$n] = ' align="left"';
2232 | else $attr[$n] = '';
2233 | }
2234 |
2235 | # Parsing span elements, including code spans, character escapes,
2236 | # and inline HTML tags, so that pipes inside those gets ignored.
2237 | $head = $this->parseSpan($head);
2238 | $headers = preg_split('/ *[|] */', $head);
2239 | $col_count = count($headers);
2240 |
2241 | # Write column headers.
2242 | $text = "\n";
2243 | $text .= "\n";
2244 | $text .= "\n";
2245 | foreach ($headers as $n => $header)
2246 | $text .= " ".$this->runSpanGamut(trim($header))." \n";
2247 | $text .= " \n";
2248 | $text .= "\n";
2249 |
2250 | # Split content by row.
2251 | $rows = explode("\n", trim($content, "\n"));
2252 |
2253 | $text .= "\n";
2254 | foreach ($rows as $row) {
2255 | # Parsing span elements, including code spans, character escapes,
2256 | # and inline HTML tags, so that pipes inside those gets ignored.
2257 | $row = $this->parseSpan($row);
2258 |
2259 | # Split row by cell.
2260 | $row_cells = preg_split('/ *[|] */', $row, $col_count);
2261 | $row_cells = array_pad($row_cells, $col_count, '');
2262 |
2263 | $text .= "\n";
2264 | foreach ($row_cells as $n => $cell)
2265 | $text .= " ".$this->runSpanGamut(trim($cell))." \n";
2266 | $text .= " \n";
2267 | }
2268 | $text .= "\n";
2269 | $text .= "
";
2270 |
2271 | return $this->hashBlock($text) . "\n";
2272 | }
2273 |
2274 |
2275 | function doDefLists($text) {
2276 | #
2277 | # Form HTML definition lists.
2278 | #
2279 | $less_than_tab = $this->tab_width - 1;
2280 |
2281 | # Re-usable pattern to match any entire dl list:
2282 | $whole_list_re = '(?>
2283 | ( # $1 = whole list
2284 | ( # $2
2285 | [ ]{0,'.$less_than_tab.'}
2286 | ((?>.*\S.*\n)+) # $3 = defined term
2287 | \n?
2288 | [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
2289 | )
2290 | (?s:.+?)
2291 | ( # $4
2292 | \z
2293 | |
2294 | \n{2,}
2295 | (?=\S)
2296 | (?! # Negative lookahead for another term
2297 | [ ]{0,'.$less_than_tab.'}
2298 | (?: \S.*\n )+? # defined term
2299 | \n?
2300 | [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
2301 | )
2302 | (?! # Negative lookahead for another definition
2303 | [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
2304 | )
2305 | )
2306 | )
2307 | )'; // mx
2308 |
2309 | $text = preg_replace_callback('{
2310 | (?>\A\n?|(?<=\n\n))
2311 | '.$whole_list_re.'
2312 | }mx',
2313 | array(&$this, '_doDefLists_callback'), $text);
2314 |
2315 | return $text;
2316 | }
2317 | function _doDefLists_callback($matches) {
2318 | # Re-usable patterns to match list item bullets and number markers:
2319 | $list = $matches[1];
2320 |
2321 | # Turn double returns into triple returns, so that we can make a
2322 | # paragraph for the last item in a list, if necessary:
2323 | $result = trim($this->processDefListItems($list));
2324 | $result = "\n" . $result . "\n
";
2325 | return $this->hashBlock($result) . "\n\n";
2326 | }
2327 |
2328 |
2329 | function processDefListItems($list_str) {
2330 | #
2331 | # Process the contents of a single definition list, splitting it
2332 | # into individual term and definition list items.
2333 | #
2334 | $less_than_tab = $this->tab_width - 1;
2335 |
2336 | # trim trailing blank lines:
2337 | $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str);
2338 |
2339 | # Process definition terms.
2340 | $list_str = preg_replace_callback('{
2341 | (?>\A\n?|\n\n+) # leading line
2342 | ( # definition terms = $1
2343 | [ ]{0,'.$less_than_tab.'} # leading whitespace
2344 | (?![:][ ]|[ ]) # negative lookahead for a definition
2345 | # mark (colon) or more whitespace.
2346 | (?> \S.* \n)+? # actual term (not whitespace).
2347 | )
2348 | (?=\n?[ ]{0,3}:[ ]) # lookahead for following line feed
2349 | # with a definition mark.
2350 | }xm',
2351 | array(&$this, '_processDefListItems_callback_dt'), $list_str);
2352 |
2353 | # Process actual definitions.
2354 | $list_str = preg_replace_callback('{
2355 | \n(\n+)? # leading line = $1
2356 | ( # marker space = $2
2357 | [ ]{0,'.$less_than_tab.'} # whitespace before colon
2358 | [:][ ]+ # definition mark (colon)
2359 | )
2360 | ((?s:.+?)) # definition text = $3
2361 | (?= \n+ # stop at next definition mark,
2362 | (?: # next term or end of text
2363 | [ ]{0,'.$less_than_tab.'} [:][ ] |
2364 | | \z
2365 | )
2366 | )
2367 | }xm',
2368 | array(&$this, '_processDefListItems_callback_dd'), $list_str);
2369 |
2370 | return $list_str;
2371 | }
2372 | function _processDefListItems_callback_dt($matches) {
2373 | $terms = explode("\n", trim($matches[1]));
2374 | $text = '';
2375 | foreach ($terms as $term) {
2376 | $term = $this->runSpanGamut(trim($term));
2377 | $text .= "\n" . $term . " ";
2378 | }
2379 | return $text . "\n";
2380 | }
2381 | function _processDefListItems_callback_dd($matches) {
2382 | $leading_line = $matches[1];
2383 | $marker_space = $matches[2];
2384 | $def = $matches[3];
2385 |
2386 | if ($leading_line || preg_match('/\n{2,}/', $def)) {
2387 | # Replace marker with the appropriate whitespace indentation
2388 | $def = str_repeat(' ', strlen($marker_space)) . $def;
2389 | $def = $this->runBlockGamut($this->outdent($def . "\n\n"));
2390 | $def = "\n". $def ."\n";
2391 | }
2392 | else {
2393 | $def = rtrim($def);
2394 | $def = $this->runSpanGamut($this->outdent($def));
2395 | }
2396 |
2397 | return "\n " . $def . " \n";
2398 | }
2399 |
2400 |
2401 | function doFencedCodeBlocks($text) {
2402 | #
2403 | # Adding the fenced code block syntax to regular Markdown:
2404 | #
2405 | # ~~~
2406 | # Code block
2407 | # ~~~
2408 | #
2409 | $less_than_tab = $this->tab_width;
2410 |
2411 | $text = preg_replace_callback('{
2412 | (?:\n|\A)
2413 | # 1: Opening marker
2414 | (
2415 | ~{3,} # Marker: three tilde or more.
2416 | )
2417 | [ ]* \n # Whitespace and newline following marker.
2418 |
2419 | # 2: Content
2420 | (
2421 | (?>
2422 | (?!\1 [ ]* \n) # Not a closing marker.
2423 | .*\n+
2424 | )+
2425 | )
2426 |
2427 | # Closing marker.
2428 | \1 [ ]* \n
2429 | }xm',
2430 | array(&$this, '_doFencedCodeBlocks_callback'), $text);
2431 |
2432 | return $text;
2433 | }
2434 | function _doFencedCodeBlocks_callback($matches) {
2435 | $codeblock = $matches[2];
2436 | $codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
2437 | $codeblock = preg_replace_callback('/^\n+/',
2438 | array(&$this, '_doFencedCodeBlocks_newlines'), $codeblock);
2439 | $codeblock = "$codeblock
";
2440 | return "\n\n".$this->hashBlock($codeblock)."\n\n";
2441 | }
2442 | function _doFencedCodeBlocks_newlines($matches) {
2443 | return str_repeat("
empty_element_suffix",
2444 | strlen($matches[0]));
2445 | }
2446 |
2447 |
2448 | #
2449 | # Redefining emphasis markers so that emphasis by underscore does not
2450 | # work in the middle of a word.
2451 | #
2452 | var $em_relist = array(
2453 | '' => '(?:(? '(?<=\S|^)(? '(?<=\S|^)(? '(?:(? '(?<=\S|^)(? '(?<=\S|^)(? '(?:(? '(?<=\S|^)(? '(?<=\S|^)(? tags
2473 | #
2474 | # Strip leading and trailing lines:
2475 | $text = preg_replace('/\A\n+|\n+\z/', '', $text);
2476 |
2477 | $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY);
2478 |
2479 | #
2480 | # Wrap tags and unhashify HTML blocks
2481 | #
2482 | foreach ($grafs as $key => $value) {
2483 | $value = trim($this->runSpanGamut($value));
2484 |
2485 | # Check if this should be enclosed in a paragraph.
2486 | # Clean tag hashes & block tag hashes are left alone.
2487 | $is_p = !preg_match('/^B\x1A[0-9]+B|^C\x1A[0-9]+C$/', $value);
2488 |
2489 | if ($is_p) {
2490 | $value = "
$value
";
2491 | }
2492 | $grafs[$key] = $value;
2493 | }
2494 |
2495 | # Join grafs in one text, then unhash HTML tags.
2496 | $text = implode("\n\n", $grafs);
2497 |
2498 | # Finish by removing any tag hashes still present in $text.
2499 | $text = $this->unhash($text);
2500 |
2501 | return $text;
2502 | }
2503 |
2504 |
2505 | ### Footnotes
2506 |
2507 | function stripFootnotes($text) {
2508 | #
2509 | # Strips link definitions from text, stores the URLs and titles in
2510 | # hash references.
2511 | #
2512 | $less_than_tab = $this->tab_width - 1;
2513 |
2514 | # Link defs are in the form: [^id]: url "optional title"
2515 | $text = preg_replace_callback('{
2516 | ^[ ]{0,'.$less_than_tab.'}\[\^(.+?)\][ ]?: # note_id = $1
2517 | [ ]*
2518 | \n? # maybe *one* newline
2519 | ( # text = $2 (no blank lines allowed)
2520 | (?:
2521 | .+ # actual text
2522 | |
2523 | \n # newlines but
2524 | (?!\[\^.+?\]:\s)# negative lookahead for footnote marker.
2525 | (?!\n+[ ]{0,3}\S)# ensure line is not blank and followed
2526 | # by non-indented content
2527 | )*
2528 | )
2529 | }xm',
2530 | array(&$this, '_stripFootnotes_callback'),
2531 | $text);
2532 | return $text;
2533 | }
2534 | function _stripFootnotes_callback($matches) {
2535 | $note_id = $this->fn_id_prefix . $matches[1];
2536 | $this->footnotes[$note_id] = $this->outdent($matches[2]);
2537 | return ''; # String that will replace the block
2538 | }
2539 |
2540 |
2541 | function doFootnotes($text) {
2542 | #
2543 | # Replace footnote references in $text [^id] with a special text-token
2544 | # which will be replaced by the actual footnote marker in appendFootnotes.
2545 | #
2546 | if (!$this->in_anchor) {
2547 | $text = preg_replace('{\[\^(.+?)\]}', "F\x1Afn:\\1\x1A:", $text);
2548 | }
2549 | return $text;
2550 | }
2551 |
2552 |
2553 | function appendFootnotes($text) {
2554 | #
2555 | # Append footnote list to text.
2556 | #
2557 | $text = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}',
2558 | array(&$this, '_appendFootnotes_callback'), $text);
2559 |
2560 | if (!empty($this->footnotes_ordered)) {
2561 | $text .= "\n\n";
2562 | $text .= "\n";
2563 | $text .= "
empty_element_suffix ."\n";
2564 | $text .= "\n\n";
2565 |
2566 | $attr = " rev=\"footnote\"";
2567 | if ($this->fn_backlink_class != "") {
2568 | $class = $this->fn_backlink_class;
2569 | $class = $this->encodeAttribute($class);
2570 | $attr .= " class=\"$class\"";
2571 | }
2572 | if ($this->fn_backlink_title != "") {
2573 | $title = $this->fn_backlink_title;
2574 | $title = $this->encodeAttribute($title);
2575 | $attr .= " title=\"$title\"";
2576 | }
2577 | $num = 0;
2578 |
2579 | while (!empty($this->footnotes_ordered)) {
2580 | $footnote = reset($this->footnotes_ordered);
2581 | $note_id = key($this->footnotes_ordered);
2582 | unset($this->footnotes_ordered[$note_id]);
2583 |
2584 | $footnote .= "\n"; # Need to append newline before parsing.
2585 | $footnote = $this->runBlockGamut("$footnote\n");
2586 | $footnote = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}',
2587 | array(&$this, '_appendFootnotes_callback'), $footnote);
2588 |
2589 | $attr = str_replace("%%", ++$num, $attr);
2590 | $note_id = $this->encodeAttribute($note_id);
2591 |
2592 | # Add backlink to last paragraph; create new paragraph if needed.
2593 | $backlink = "↩";
2594 | if (preg_match('{$}', $footnote)) {
2595 | $footnote = substr($footnote, 0, -4) . " $backlink";
2596 | } else {
2597 | $footnote .= "\n\n$backlink
";
2598 | }
2599 |
2600 | $text .= "- \n";
2601 | $text .= $footnote . "\n";
2602 | $text .= "
\n\n";
2603 | }
2604 |
2605 | $text .= "
\n";
2606 | $text .= "";
2607 | }
2608 | return $text;
2609 | }
2610 | function _appendFootnotes_callback($matches) {
2611 | $node_id = $this->fn_id_prefix . $matches[1];
2612 |
2613 | # Create footnote marker only if it has a corresponding footnote *and*
2614 | # the footnote hasn't been used by another marker.
2615 | if (isset($this->footnotes[$node_id])) {
2616 | # Transfert footnote content to the ordered list.
2617 | $this->footnotes_ordered[$node_id] = $this->footnotes[$node_id];
2618 | unset($this->footnotes[$node_id]);
2619 |
2620 | $num = $this->footnote_counter++;
2621 | $attr = " rel=\"footnote\"";
2622 | if ($this->fn_link_class != "") {
2623 | $class = $this->fn_link_class;
2624 | $class = $this->encodeAttribute($class);
2625 | $attr .= " class=\"$class\"";
2626 | }
2627 | if ($this->fn_link_title != "") {
2628 | $title = $this->fn_link_title;
2629 | $title = $this->encodeAttribute($title);
2630 | $attr .= " title=\"$title\"";
2631 | }
2632 |
2633 | $attr = str_replace("%%", $num, $attr);
2634 | $node_id = $this->encodeAttribute($node_id);
2635 |
2636 | return
2637 | "".
2638 | "$num".
2639 | "";
2640 | }
2641 |
2642 | return "[^".$matches[1]."]";
2643 | }
2644 |
2645 |
2646 | ### Abbreviations ###
2647 |
2648 | function stripAbbreviations($text) {
2649 | #
2650 | # Strips abbreviations from text, stores titles in hash references.
2651 | #
2652 | $less_than_tab = $this->tab_width - 1;
2653 |
2654 | # Link defs are in the form: [id]*: url "optional title"
2655 | $text = preg_replace_callback('{
2656 | ^[ ]{0,'.$less_than_tab.'}\*\[(.+?)\][ ]?: # abbr_id = $1
2657 | (.*) # text = $2 (no blank lines allowed)
2658 | }xm',
2659 | array(&$this, '_stripAbbreviations_callback'),
2660 | $text);
2661 | return $text;
2662 | }
2663 | function _stripAbbreviations_callback($matches) {
2664 | $abbr_word = $matches[1];
2665 | $abbr_desc = $matches[2];
2666 | if ($this->abbr_word_re)
2667 | $this->abbr_word_re .= '|';
2668 | $this->abbr_word_re .= preg_quote($abbr_word);
2669 | $this->abbr_desciptions[$abbr_word] = trim($abbr_desc);
2670 | return ''; # String that will replace the block
2671 | }
2672 |
2673 |
2674 | function doAbbreviations($text) {
2675 | #
2676 | # Find defined abbreviations in text and wrap them in elements.
2677 | #
2678 | if ($this->abbr_word_re) {
2679 | // cannot use the /x modifier because abbr_word_re may
2680 | // contain significant spaces:
2681 | $text = preg_replace_callback('{'.
2682 | '(?abbr_word_re.')'.
2684 | '(?![\w\x1A])'.
2685 | '}',
2686 | array(&$this, '_doAbbreviations_callback'), $text);
2687 | }
2688 | return $text;
2689 | }
2690 | function _doAbbreviations_callback($matches) {
2691 | $abbr = $matches[0];
2692 | if (isset($this->abbr_desciptions[$abbr])) {
2693 | $desc = $this->abbr_desciptions[$abbr];
2694 | if (empty($desc)) {
2695 | return $this->hashPart("$abbr");
2696 | } else {
2697 | $desc = $this->encodeAttribute($desc);
2698 | return $this->hashPart("$abbr");
2699 | }
2700 | } else {
2701 | return $matches[0];
2702 | }
2703 | }
2704 |
2705 | }
2706 |
2707 |
2708 | /*
2709 |
2710 | PHP Markdown Extra
2711 | ==================
2712 |
2713 | Description
2714 | -----------
2715 |
2716 | This is a PHP port of the original Markdown formatter written in Perl
2717 | by John Gruber. This special "Extra" version of PHP Markdown features
2718 | further enhancements to the syntax for making additional constructs
2719 | such as tables and definition list.
2720 |
2721 | Markdown is a text-to-HTML filter; it translates an easy-to-read /
2722 | easy-to-write structured text format into HTML. Markdown's text format
2723 | is most similar to that of plain text email, and supports features such
2724 | as headers, *emphasis*, code blocks, blockquotes, and links.
2725 |
2726 | Markdown's syntax is designed not as a generic markup language, but
2727 | specifically to serve as a front-end to (X)HTML. You can use span-level
2728 | HTML tags anywhere in a Markdown document, and you can use block level
2729 | HTML tags (like and as well).
2730 |
2731 | For more information about Markdown's syntax, see:
2732 |
2733 |
2734 |
2735 |
2736 | Bugs
2737 | ----
2738 |
2739 | To file bug reports please send email to:
2740 |
2741 |
2742 |
2743 | Please include with your report: (1) the example input; (2) the output you
2744 | expected; (3) the output Markdown actually produced.
2745 |
2746 |
2747 | Version History
2748 | ---------------
2749 |
2750 | See the readme file for detailed release notes for this version.
2751 |
2752 |
2753 | Copyright and License
2754 | ---------------------
2755 |
2756 | PHP Markdown & Extra
2757 | Copyright (c) 2004-2009 Michel Fortin
2758 |
2759 | All rights reserved.
2760 |
2761 | Based on Markdown
2762 | Copyright (c) 2003-2006 John Gruber
2763 |
2764 | All rights reserved.
2765 |
2766 | Redistribution and use in source and binary forms, with or without
2767 | modification, are permitted provided that the following conditions are
2768 | met:
2769 |
2770 | * Redistributions of source code must retain the above copyright notice,
2771 | this list of conditions and the following disclaimer.
2772 |
2773 | * Redistributions in binary form must reproduce the above copyright
2774 | notice, this list of conditions and the following disclaimer in the
2775 | documentation and/or other materials provided with the distribution.
2776 |
2777 | * Neither the name "Markdown" nor the names of its contributors may
2778 | be used to endorse or promote products derived from this software
2779 | without specific prior written permission.
2780 |
2781 | This software is provided by the copyright holders and contributors "as
2782 | is" and any express or implied warranties, including, but not limited
2783 | to, the implied warranties of merchantability and fitness for a
2784 | particular purpose are disclaimed. In no event shall the copyright owner
2785 | or contributors be liable for any direct, indirect, incidental, special,
2786 | exemplary, or consequential damages (including, but not limited to,
2787 | procurement of substitute goods or services; loss of use, data, or
2788 | profits; or business interruption) however caused and on any theory of
2789 | liability, whether in contract, strict liability, or tort (including
2790 | negligence or otherwise) arising in any way out of the use of this
2791 | software, even if advised of the possibility of such damage.
2792 |
2793 | */
2794 | ?>
--------------------------------------------------------------------------------