if (typeof markdown === 'undefined') var markdown = {
    parse: function (s) {
        var r = s, ii, pre1 = [], pre2 = [];
        // detect newline format
        var newline = r.indexOf('\r\n') != -1 ? '\r\n' : r.indexOf('\n') != -1 ? '\n' : ''
        
        // store {{{ unformatted blocks }}} and 
 pre-formatted blocks 
        r = r.replace(/{{{([\s\S]*?)}}}/g, function (x) { pre1.push(x.substring(3, x.length - 3)); return '{{{}}}'; });
        r = r.replace(new RegExp('([\\s\\S]*?)
', 'gi'), function (x) { pre2.push(x.substring(5, x.length - 6)); return ''; });
        
        // h1 - h4 and hr
        r = r.replace(/^==== (.*)=*/gm, '$1
');
        r = r.replace(/^=== (.*)=*/gm, '$1
');
        r = r.replace(/^== (.*)=*/gm, '$1
');
        r = r.replace(/^= (.*)=*/gm, '$1
');
        r = r.replace(/^[-*][-*][-*]+/gm, '
');
        
        // bold, italics, and code formatting
        r = r.replace(/\*\*(.*?)\*\*/g, '$1');
        r = r.replace(new RegExp('//(((?!https?://).)*?)//', 'g'), '$1');
        r = r.replace(/``(.*?)``/g, '$1');
        
        // unordered lists
        r = r.replace(/^\*\*\*\* (.*)/gm, '');
        r = r.replace(/^\*\*\* (.*)/gm, '');
        r = r.replace(/^\*\* (.*)/gm, '');
        r = r.replace(/^\* (.*)/gm, '');
        for (ii = 0; ii < 3; ii++) r = r.replace(new RegExp('' + newline + '', 'g'), newline);
        
        // ordered lists
        r = r.replace(/^#### (.*)/gm, '- $1
');
        r = r.replace(/^### (.*)/gm, '- $1
');
        r = r.replace(/^## (.*)/gm, '- $1
');
        r = r.replace(/^# (.*)/gm, '- $1
');
        for (ii = 0; ii < 3; ii++) r = r.replace(new RegExp('' + newline + '', 'g'), newline);
        
        // links
        r = r.replace(/\[\[(http:[^\]|]*?)\]\]/g, '$1');
        r = r.replace(/\[\[(http:[^|]*?)\|(.*?)\]\]/g, '$2');
        r = r.replace(/\[\[([^\]|]*?)\]\]/g, '$1');
        r = r.replace(/\[\[([^|]*?)\|(.*?)\]\]/g, '$2');
        
        // images
        r = r.replace(/{{([^\]|]*?)}}/g, ' ');
        r = r.replace(/{{([^|]*?)\|(.*?)}}/g, '
');
        r = r.replace(/{{([^|]*?)\|(.*?)}}/g, ' ');
        
        // video
        r = r.replace(/<<(.*?)>>/g, '');
        
        // hard linebreak if there are 2 or more spaces at the end of a line
        r = r.replace(new RegExp(' + ' + newline, 'g'), '
');
        
        // video
        r = r.replace(/<<(.*?)>>/g, '');
        
        // hard linebreak if there are 2 or more spaces at the end of a line
        r = r.replace(new RegExp(' + ' + newline, 'g'), '
' + newline);
        
        // split on double-newlines, then add paragraph tags when the first tag isn't a block level element
        if (newline != '') for (var p = r.split(newline + newline), i = 0; i < p.length; i++) {
            var blockLevel = false;
            if (p[i].length >= 1 && p[i].charAt(0) == '<') {
                // check if the first tag is a block-level element
                var firstSpace = p[i].indexOf(' '), firstCloseTag = p[i].indexOf('>');
                var endIndex = firstSpace > -1 && firstCloseTag > -1 ? Math.min(firstSpace, firstCloseTag) : firstSpace > -1 ? firstSpace : firstCloseTag;
                var tag = p[i].substring(1, endIndex).toLowerCase();
                for (var j = 0; j < blockLevelElements.length; j++) if (blockLevelElements[j] == tag) blockLevel = true;
            } else if (p[i].length >= 1 && p[i].charAt(0) == '|') {
                // format the paragraph as a table
                blockLevel = true;
                p[i] = p[i].replace(/ \|= /g, '').replace(/\|= /g, ' | | ').replace(/ \|=/g, ' | 
|---|
');
                p[i] = p[i].replace(/ \| /g, '').replace(/\| /g, ' | | ').replace(/ \|/g, ' | 
');
                p[i] = '';
            } else if (p[i].length >= 2 && p[i].charAt(0) == '>' && p[i].charAt(1) == ' ') {
                // format the paragraph as a blockquote
                blockLevel = true;
                p[i] = '' + p[i].replace(/^> /gm, '') + '
';
            }
            if (!blockLevel) p[i] = '' + p[i] + '
';
        }
        
        // reassemble the paragraphs
        if (newline != '') r = p.join(newline + newline);
        
        // restore the preformatted and unformatted blocks
        r = r.replace(new RegExp('', 'g'), function (match) { return '' + pre2.shift() + '
'; });
        r = r.replace(/{{{}}}/g, function (match) { return pre1.shift(); });
        return r;
    }
};