//
// showdown.js -- A javascript port of Markdown.
//
// Copyright (c) 2007 John Fraser.
//
// Original Markdown Copyright (c) 2004-2005 John Gruber
// tags get encoded.
//
// Clear the global hashes. If we don't clear these, you get conflicts
// from other articles when generating a page which contains more than
// one article (e.g. an index page that shows the N most recent
// articles):
g_urls = new Array();
g_titles = new Array();
g_html_blocks = new Array();
// attacklab: Replace ~ with ~T
// This lets us use tilde as an escape char to avoid md5 hashes
// The choice of character is arbitray; anything that isn't
// magic in Markdown will work.
text = text.replace(/~/g,"~T");
// attacklab: Replace $ with ~D
// RegExp interprets $ as a special character
// when it's in a replacement string
text = text.replace(/\$/g,"~D");
// Standardize line endings
text = text.replace(/\r\n/g,"\n"); // DOS to Unix
text = text.replace(/\r/g,"\n"); // Mac to Unix
// Make sure text begins and ends with a couple of newlines:
text = "\n\n" + text + "\n\n";
// Convert all tabs to spaces.
text = _Detab(text);
// Strip any lines consisting only of spaces and tabs.
// This makes subsequent regexen easier to write, because we can
// match consecutive blank lines with /\n+/ instead of something
// contorted like /[ \t]*\n+/ .
text = text.replace(/^[ \t]+$/mg,"");
// Turn block-level HTML blocks into hash entries
text = _HashHTMLBlocks(text);
// Strip link definitions, store in hashes.
text = _StripLinkDefinitions(text);
text = _RunBlockGamut(text);
text = _UnescapeSpecialChars(text);
// attacklab: Restore dollar signs
text = text.replace(/~D/g,"$$");
// attacklab: Restore tildes
text = text.replace(/~T/g,"~");
// ** GFM ** Auto-link URLs and emails
text = text.replace(/https?\:\/\/[^"\s\<\>]*[^.,;'">\:\s\<\>\)\]\!]/g, function(wholeMatch,matchIndex){
var left = text.slice(0, matchIndex), right = text.slice(matchIndex)
if (left.match(/<[^>]+$/) && right.match(/^[^>]*>/)) {return wholeMatch}
return "" + wholeMatch + "";
});
text = text.replace(/[a-z0-9_\-+=.]+@[a-z0-9\-]+(\.[a-z0-9-]+)+/ig, function(wholeMatch){return "" + wholeMatch + "";});
// ** GFM ** Auto-link sha1 if GitHub.nameWithOwner is defined
text = text.replace(/[a-f0-9]{40}/ig, function(wholeMatch,matchIndex){
if (typeof(GitHub) == "undefined" || typeof(GitHub.nameWithOwner) == "undefined") {return wholeMatch;}
var left = text.slice(0, matchIndex), right = text.slice(matchIndex)
if (left.match(/@$/) || (left.match(/<[^>]+$/) && right.match(/^[^>]*>/))) {return wholeMatch;}
return "" + wholeMatch.substring(0,7) + "";
});
// ** GFM ** Auto-link user@sha1 if GitHub.nameWithOwner is defined
text = text.replace(/([a-z0-9_\-+=.]+)@([a-f0-9]{40})/ig, function(wholeMatch,username,sha,matchIndex){
if (typeof(GitHub) == "undefined" || typeof(GitHub.nameWithOwner) == "undefined") {return wholeMatch;}
GitHub.repoName = GitHub.repoName || _GetRepoName()
var left = text.slice(0, matchIndex), right = text.slice(matchIndex)
if (left.match(/\/$/) || (left.match(/<[^>]+$/) && right.match(/^[^>]*>/))) {return wholeMatch;}
return "" + username + "@" + sha.substring(0,7) + "";
});
// ** GFM ** Auto-link user/repo@sha1
text = text.replace(/([a-z0-9_\-+=.]+\/[a-z0-9_\-+=.]+)@([a-f0-9]{40})/ig, function(wholeMatch,repo,sha){
return "" + repo + "@" + sha.substring(0,7) + "";
});
// ** GFM ** Auto-link #issue if GitHub.nameWithOwner is defined
text = text.replace(/#([0-9]+)/ig, function(wholeMatch,issue,matchIndex){
if (typeof(GitHub) == "undefined" || typeof(GitHub.nameWithOwner) == "undefined") {return wholeMatch;}
var left = text.slice(0, matchIndex), right = text.slice(matchIndex)
if (left == "" || left.match(/[a-z0-9_\-+=.]$/) || (left.match(/<[^>]+$/) && right.match(/^[^>]*>/))) {return wholeMatch;}
return "" + wholeMatch + "";
});
// ** GFM ** Auto-link user#issue if GitHub.nameWithOwner is defined
text = text.replace(/([a-z0-9_\-+=.]+)#([0-9]+)/ig, function(wholeMatch,username,issue,matchIndex){
if (typeof(GitHub) == "undefined" || typeof(GitHub.nameWithOwner) == "undefined") {return wholeMatch;}
GitHub.repoName = GitHub.repoName || _GetRepoName()
var left = text.slice(0, matchIndex), right = text.slice(matchIndex)
if (left.match(/\/$/) || (left.match(/<[^>]+$/) && right.match(/^[^>]*>/))) {return wholeMatch;}
return "" + wholeMatch + "";
});
// ** GFM ** Auto-link user/repo#issue
text = text.replace(/([a-z0-9_\-+=.]+\/[a-z0-9_\-+=.]+)#([0-9]+)/ig, function(wholeMatch,repo,issue){
return "" + wholeMatch + "";
});
return text;
}
var _GetRepoName = function() {
return GitHub.nameWithOwner.match(/^.+\/(.+)$/)[1]
}
var _StripLinkDefinitions = function(text) {
//
// Strips link definitions from text, stores the URLs and titles in
// hash references.
//
// Link defs are in the form: ^[id]: url "optional title"
/*
var text = text.replace(/
^[ ]{0,3}\[(.+)\]: // id = $1 attacklab: g_tab_width - 1
[ \t]*
\n? // maybe *one* newline
[ \t]*
(\S+?)>? // url = $2
[ \t]*
\n? // maybe one newline
[ \t]*
(?:
(\n*) // any lines skipped = $3 attacklab: lookbehind removed
["(]
(.+?) // title = $4
[")]
[ \t]*
)? // title is optional
(?:\n+|$)
/gm,
function(){...});
*/
var text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm,
function (wholeMatch,m1,m2,m3,m4) {
m1 = m1.toLowerCase();
g_urls[m1] = _EncodeAmpsAndAngles(m2); // Link IDs are case-insensitive
if (m3) {
// Oops, found blank lines, so it's not a title.
// Put back the parenthetical statement we stole.
return m3+m4;
} else if (m4) {
g_titles[m1] = m4.replace(/"/g,""");
}
// Completely remove the definition from the text
return "";
}
);
return text;
}
var _HashHTMLBlocks = function(text) {
// attacklab: Double up blank lines to reduce lookaround
text = text.replace(/\n/g,"\n\n");
// Hashify HTML blocks:
// We only want to do this for block-level HTML tags, such as headers,
// lists, and tables. That's because we still want to wrap
s around // "paragraphs" that are wrapped in non-block-level tags, such as anchors, // phrase emphasis, and spans. The list of tags we're looking for is // hard-coded: var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del" var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math" // First, look for nested blocks, e.g.: //
tags around block-level tags.
text = _HashHTMLBlocks(text);
text = _FormParagraphs(text);
return text;
}
var _RunSpanGamut = function(text) {
//
// These are all the transformations that occur *within* block-level
// tags like paragraphs, headers, and list items.
//
text = _DoCodeSpans(text);
text = _EscapeSpecialCharsWithinTagAttributes(text);
text = _EncodeBackslashEscapes(text);
// Process anchor and image tags. Images must come first,
// because ![foo][f] looks like an anchor.
text = _DoImages(text);
text = _DoAnchors(text);
// Make links out of things like ` Just type tags
//
// Strip leading and trailing lines:
text = text.replace(/^\n+/g,"");
text = text.replace(/\n+$/g,"");
var grafs = text.split(/\n{2,}/g);
var grafsOut = new Array();
//
// Wrap tags.
//
var end = grafs.length;
for (var i=0; i ");
str += "
\n");
return text;
}
var _EscapeSpecialCharsWithinTagAttributes = function(text) {
//
// Within tags -- meaning between < and > -- encode [\ ` * _] so they
// don't conflict with their use in Markdown for code, italics and strong.
//
// Build a regex to find HTML tags and comments. See Friedl's
// "Mastering Regular Expressions", 2nd Ed., pp. 200-201.
var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|)/gi;
text = text.replace(regex, function(wholeMatch) {
var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g,"$1`");
tag = escapeCharacters(tag,"\\`*_");
return tag;
});
return text;
}
var _DoAnchors = function(text) {
//
// Turn Markdown link shortcuts into XHTML tags.
//
//
// First, handle reference-style links: [link text] [id]
//
/*
text = text.replace(/
( // wrap whole match in $1
\[
(
(?:
\[[^\]]*\] // allow brackets nested one level
|
[^\[] // or anything else
)*
)
\]
[ ]? // one optional space
(?:\n[ ]*)? // one optional newline followed by spaces
\[
(.*?) // id = $3
\]
)()()()() // pad remaining backreferences
/g,_DoAnchors_callback);
*/
text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeAnchorTag);
//
// Next, inline-style links: [link text](url "optional title")
//
/*
text = text.replace(/
( // wrap whole match in $1
\[
(
(?:
\[[^\]]*\] // allow brackets nested one level
|
[^\[\]] // or anything else
)
)
\]
\( // literal paren
[ \t]*
() // no id, so leave $3 empty
(.*?)>? // href = $4
[ \t]*
( // $5
(['"]) // quote char = $6
(.*?) // Title = $7
\6 // matching quote
[ \t]* // ignore any spaces/tabs between closing quote and )
)? // title is optional
\)
)
/g,writeAnchorTag);
*/
text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()(.*?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeAnchorTag);
//
// Last, handle reference-style shortcuts: [link text]
// These must come last in case you've also got [link test][1]
// or [link test](/foo)
//
/*
text = text.replace(/
( // wrap whole match in $1
\[
([^\[\]]+) // link text = $2; can't contain '[' or ']'
\]
)()()()()() // pad rest of backreferences
/g, writeAnchorTag);
*/
text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag);
return text;
}
var writeAnchorTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) {
if (m7 == undefined) m7 = "";
var whole_match = m1;
var link_text = m2;
var link_id = m3.toLowerCase();
var url = m4;
var title = m7;
if (url == "") {
if (link_id == "") {
// lower-case and turn embedded newlines into spaces
link_id = link_text.toLowerCase().replace(/ ?\n/g," ");
}
url = "#"+link_id;
if (g_urls[link_id] != undefined) {
url = g_urls[link_id];
if (g_titles[link_id] != undefined) {
title = g_titles[link_id];
}
}
else {
if (whole_match.search(/\(\s*\)$/m)>-1) {
// Special case for explicit empty url
url = "";
} else {
return whole_match;
}
}
}
url = escapeCharacters(url,"*_");
var result = "" + link_text + "";
return result;
}
var _DoImages = function(text) {
//
// Turn Markdown image shortcuts into tags.
//
//
// First, handle reference-style labeled images: ![alt text][id]
//
/*
text = text.replace(/
( // wrap whole match in $1
!\[
(.*?) // alt text = $2
\]
[ ]? // one optional space
(?:\n[ ]*)? // one optional newline followed by spaces
\[
(.*?) // id = $3
\]
)()()()() // pad rest of backreferences
/g,writeImageTag);
*/
text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeImageTag);
//
// Next, handle inline images: 
// Don't forget: encode * and _
/*
text = text.replace(/
( // wrap whole match in $1
!\[
(.*?) // alt text = $2
\]
\s? // One optional whitespace character
\( // literal paren
[ \t]*
() // no id, so leave $3 empty
(\S+?)>? // src url = $4
[ \t]*
( // $5
(['"]) // quote char = $6
(.*?) // title = $7
\6 // matching quote
[ \t]*
)? // title is optional
\)
)
/g,writeImageTag);
*/
text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()(\S+?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeImageTag);
return text;
}
var writeImageTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) {
var whole_match = m1;
var alt_text = m2;
var link_id = m3.toLowerCase();
var url = m4;
var title = m7;
if (!title) title = "";
if (url == "") {
if (link_id == "") {
// lower-case and turn embedded newlines into spaces
link_id = alt_text.toLowerCase().replace(/ ?\n/g," ");
}
url = "#"+link_id;
if (g_urls[link_id] != undefined) {
url = g_urls[link_id];
if (g_titles[link_id] != undefined) {
title = g_titles[link_id];
}
}
else {
return whole_match;
}
}
alt_text = alt_text.replace(/"/g,""");
url = escapeCharacters(url,"*_");
var result = "
";
return result;
}
var _DoHeaders = function(text) {
// Setext-style headers:
// Header 1
// ========
//
// Header 2
// --------
//
text = text.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm,
function(wholeMatch,m1){return hashBlock("
" + _RunSpanGamut(m1) + "
");});
text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,
function(matchFound,m1){return hashBlock("" + _RunSpanGamut(m1) + "
");});
// atx-style headers:
// # Header 1
// ## Header 2
// ## Header 2 with closing hashes ##
// ...
// ###### Header 6
//
/*
text = text.replace(/
^(\#{1,6}) // $1 = string of #'s
[ \t]*
(.+?) // $2 = Header text
[ \t]*
\#* // optional closing #'s (not counted)
\n+
/gm, function() {...});
*/
text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,
function(wholeMatch,m1,m2) {
var h_level = m1.length;
return hashBlock("` blocks.
//
/*
text = text.replace(text,
/(?:\n\n|^)
( // $1 = the code block -- one or more lines, starting with a space/tab
(?:
(?:[ ]{4}|\t) // Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
.*\n+
)+
)
(\n*[ ]{0,3}[^ \t\n]|(?=~0)) // attacklab: g_tab_width
/g,function(){...});
*/
// attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
text += "~0";
text = text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,
function(wholeMatch,m1,m2) {
var codeblock = m1;
var nextChar = m2;
codeblock = _EncodeCode( _Outdent(codeblock));
codeblock = _Detab(codeblock);
codeblock = codeblock.replace(/^\n+/g,""); // trim leading newlines
codeblock = codeblock.replace(/\n+$/g,""); // trim trailing whitespace
codeblock = "
";
return hashBlock(codeblock) + nextChar;
}
);
// attacklab: strip sentinel
text = text.replace(/~0/,"");
return text;
}
var hashBlock = function(text) {
text = text.replace(/(^\n+|\n+$)/g,"");
return "\n\n~K" + (g_html_blocks.push(text)-1) + "K\n\n";
}
var _DoCodeSpans = function(text) {
//
// * Backtick quotes are used for " + codeblock + "\n
spans.
//
// * You can use multiple backticks as the delimiters if you want to
// include literal backticks in the code span. So, this input:
//
// Just type ``foo `bar` baz`` at the prompt.
//
// Will translate to:
//
//
foo `bar` baz
at the prompt.`bar`
...
//
/*
text = text.replace(/
(^|[^\\]) // Character before opening ` can't be a backslash
(`+) // $2 = Opening run of `
( // $3 = The code block
[^\r]*?
[^`] // attacklab: work around lack of lookbehind
)
\2 // Matching closer
(?!`)
/gm, function(){...});
*/
text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,
function(wholeMatch,m1,m2,m3,m4) {
var c = m3;
c = c.replace(/^([ \t]*)/g,""); // leading whitespace
c = c.replace(/[ \t]*$/g,""); // trailing whitespace
c = _EncodeCode(c);
return m1+""+c+"
";
});
return text;
}
var _EncodeCode = function(text) {
//
// Encode/escape certain characters inside Markdown code runs.
// The point is that in code, these characters are literals,
// and lose their special Markdown meanings.
//
// Encode all ampersands; HTML entities are not
// entities within a Markdown code span.
text = text.replace(/&/g,"&");
// Do the angle bracket song and dance:
text = text.replace(//g,">");
// Now, escape characters that are magic in Markdown:
text = escapeCharacters(text,"\*_{}[]\\",false);
// jj the line above breaks this:
//---
//* Item
// 1. Subitem
// special char: *
//---
return text;
}
var _DoItalicsAndBold = function(text) {
// must go first:
text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g,
"$2");
text = text.replace(/(\w)_(\w)/g, "$1~E95E$2") // ** GFM ** "~E95E" == escaped "_"
text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g,
"$2");
return text;
}
var _DoBlockQuotes = function(text) {
/*
text = text.replace(/
( // Wrap whole match in $1
(
^[ \t]*>[ \t]? // '>' at the start of a line
.+\n // rest of the first line
(.+\n)* // subsequent consecutive lines
\n* // blanks
)+
)
/gm, function(){...});
*/
text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,
function(wholeMatch,m1) {
var bq = m1;
// attacklab: hack around Konqueror 3.5.4 bug:
// "----------bug".replace(/^-/g,"") == "bug"
bq = bq.replace(/^[ \t]*>[ \t]?/gm,"~0"); // trim one level of quoting
// attacklab: clean up hack
bq = bq.replace(/~0/g,"");
bq = bq.replace(/^[ \t]+$/gm,""); // trim whitespace-only lines
bq = _RunBlockGamut(bq); // recurse
bq = bq.replace(/(^|\n)/g,"$1 ");
// These leading spaces screw with content, so we need to fix that:
bq = bq.replace(
/(\s*
[^\r]+?<\/pre>)/gm,
function(wholeMatch,m1) {
var pre = m1;
// attacklab: hack around Konqueror 3.5.4 bug:
pre = pre.replace(/^ /mg,"~0");
pre = pre.replace(/~0/g,"");
return pre;
});
return hashBlock("
\n" + bq + "\n
");
});
return text;
}
var _FormParagraphs = function(text) {
//
// Params:
// $text - string to process with html
"); // ** GFM **
str = str.replace(/^([ \t]*)/g,"