diff options
Diffstat (limited to 'lib/showdown')
| -rw-r--r-- | lib/showdown/index.js | 6 | ||||
| -rw-r--r-- | lib/showdown/showdown-0.9.js | 1296 | 
2 files changed, 1302 insertions, 0 deletions
diff --git a/lib/showdown/index.js b/lib/showdown/index.js new file mode 100644 index 00000000..59a97233 --- /dev/null +++ b/lib/showdown/index.js @@ -0,0 +1,6 @@ +var fs = require('fs'); + +var filename = __dirname + '/showdown-0.9.js'; +var src = fs.readFileSync(filename); +var Showdown = process.compile(src + '\nShowdown;', filename); +exports.Showdown = Showdown; diff --git a/lib/showdown/showdown-0.9.js b/lib/showdown/showdown-0.9.js new file mode 100644 index 00000000..1bab5b39 --- /dev/null +++ b/lib/showdown/showdown-0.9.js @@ -0,0 +1,1296 @@ +// +// showdown.js -- A javascript port of Markdown. +// +// Copyright (c) 2007 John Fraser. +// +// Original Markdown Copyright (c) 2004-2005 John Gruber +//   <http://daringfireball.net/projects/markdown/> +// +// Redistributable under a BSD-style open source license. +// See license.txt for more information. +// +// The full source distribution is at: +// +//        A A L +//        T C A +//        T K B +// +//   <http://www.attacklab.net/> +// + +// +// Wherever possible, Showdown is a straight, line-by-line port +// of the Perl version of Markdown. +// +// This is not a normal parser design; it's basically just a +// series of string substitutions.  It's hard to read and +// maintain this way,  but keeping Showdown close to the original +// design makes it easier to port new features. +// +// More importantly, Showdown behaves like markdown.pl in most +// edge cases.  So web applications can do client-side preview +// in Javascript, and then build identical HTML on the server. +// +// This port needs the new RegExp functionality of ECMA 262, +// 3rd Edition (i.e. Javascript 1.5).  Most modern web browsers +// should do fine.  Even with the new regular expression features, +// We do a lot of work to emulate Perl's regex functionality. +// The tricky changes in this file mostly have the "attacklab:" +// label.  Major or self-explanatory changes don't. +// +// Smart diff tools like Araxis Merge will be able to match up +// this file with markdown.pl in a useful way.  A little tweaking +// helps: in a copy of markdown.pl, replace "#" with "//" and +// replace "$text" with "text".  Be sure to ignore whitespace +// and line endings. +// + + +// +// Showdown usage: +// +//   var text = "Markdown *rocks*."; +// +//   var converter = new Showdown.converter(); +//   var html = converter.makeHtml(text); +// +//   alert(html); +// +// Note: move the sample code to the bottom of this +// file before uncommenting it. +// + + +// +// Showdown namespace +// +var Showdown = {}; + +// +// converter +// +// Wraps all "globals" so that the only thing +// exposed is makeHtml(). +// +Showdown.converter = function() { + +// +// Globals: +// + +// Global hashes, used by various utility routines +var g_urls; +var g_titles; +var g_html_blocks; + +// Used to track when we're inside an ordered or unordered list +// (see _ProcessListItems() for details): +var g_list_level = 0; + + +this.makeHtml = function(text) { +// +// Main function. The order in which other subs are called here is +// essential. Link and image substitutions need to happen before +// _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the <a> +// and <img> 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,"~"); + +  return text; +} + + +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 <p>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.: +  //   <div> +  //     <div> +  //     tags for inner block must be indented. +  //     </div> +  //   </div> +  // +  // The outermost tags must start at the left margin for this to match, and +  // the inner nested divs must be indented. +  // We need to do this before the next, more liberal match, because the next +  // match will start at the first `<div>` and stop at the first `</div>`. + +  // attacklab: This regex can be expensive when it fails. +  /* +    var text = text.replace(/ +    (           // save in $1 +      ^         // start of line  (with /m) +      <($block_tags_a)  // start tag = $2 +      \b          // word break +                // attacklab: hack around khtml/pcre bug... +      [^\r]*?\n     // any number of lines, minimally matching +      </\2>       // the matching end tag +      [ \t]*        // trailing spaces/tabs +      (?=\n+)       // followed by a newline +    )           // attacklab: there are sentinel newlines at end of document +    /gm,function(){...}}; +  */ +  text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm,hashElement); + +  // +  // Now match more liberally, simply from `\n<tag>` to `</tag>\n` +  // + +  /* +    var text = text.replace(/ +    (           // save in $1 +      ^         // start of line  (with /m) +      <($block_tags_b)  // start tag = $2 +      \b          // word break +                // attacklab: hack around khtml/pcre bug... +      [^\r]*?       // any number of lines, minimally matching +      .*</\2>       // the matching end tag +      [ \t]*        // trailing spaces/tabs +      (?=\n+)       // followed by a newline +    )           // attacklab: there are sentinel newlines at end of document +    /gm,function(){...}}; +  */ +  text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm,hashElement); + +  // Special case just for <hr />. It was easier to make a special case than +  // to make the other regex more complicated.   + +  /* +    text = text.replace(/ +    (           // save in $1 +      \n\n        // Starting after a blank line +      [ ]{0,3} +      (<(hr)        // start tag = $2 +      \b          // word break +      ([^<>])*?     //  +      \/?>)       // the matching end tag +      [ \t]* +      (?=\n{2,})      // followed by a blank line +    ) +    /g,hashElement); +  */ +  text = text.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,hashElement); + +  // Special case for standalone HTML comments: + +  /* +    text = text.replace(/ +    (           // save in $1 +      \n\n        // Starting after a blank line +      [ ]{0,3}      // attacklab: g_tab_width - 1 +      <! +      (--[^\r]*?--\s*)+ +      > +      [ \t]* +      (?=\n{2,})      // followed by a blank line +    ) +    /g,hashElement); +  */ +  text = text.replace(/(\n\n[ ]{0,3}<!(--[^\r]*?--\s*)+>[ \t]*(?=\n{2,}))/g,hashElement); + +  // PHP and ASP-style processor instructions (<?...?> and <%...%>) + +  /* +    text = text.replace(/ +    (?: +      \n\n        // Starting after a blank line +    ) +    (           // save in $1 +      [ ]{0,3}      // attacklab: g_tab_width - 1 +      (?: +        <([?%])     // $2 +        [^\r]*? +        \2> +      ) +      [ \t]* +      (?=\n{2,})      // followed by a blank line +    ) +    /g,hashElement); +  */ +  text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,hashElement); + +  // attacklab: Undo double lines (see comment at top of this function) +  text = text.replace(/\n\n/g,"\n"); +  return text; +} + +var hashElement = function(wholeMatch,m1) { +  var blockText = m1; + +  // Undo double lines +  blockText = blockText.replace(/\n\n/g,"\n"); +  blockText = blockText.replace(/^\n/,""); +   +  // strip trailing blank lines +  blockText = blockText.replace(/\n+$/g,""); +   +  // Replace the element text with a marker ("~KxK" where x is its key) +  blockText = "\n\n~K" + (g_html_blocks.push(blockText)-1) + "K\n\n"; +   +  return blockText; +}; + +var _RunBlockGamut = function(text) { +// +// These are all the transformations that form block-level +// tags like paragraphs, headers, and list items. +// +  text = _DoHeaders(text); + +  // Do Horizontal Rules: +  var key = hashBlock("<hr />"); +  text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm,key); +  text = text.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm,key); +  text = text.replace(/^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$/gm,key); + +  text = _DoLists(text); +  text = _DoCodeBlocks(text); +  text = _DoBlockQuotes(text); + +  // We already ran _HashHTMLBlocks() before, in Markdown(), but that +  // was to escape raw HTML in the original Markdown source. This time, +  // we're escaping the markup we've just created, so that we don't wrap +  // <p> 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 `<http://example.com/>` +  // Must come after _DoAnchors(), because you can use < and > +  // delimiters in inline links like [this](<url>). +  text = _DoAutoLinks(text); +  text = _EncodeAmpsAndAngles(text); +  text = _DoItalicsAndBold(text); + +  // Do hard breaks: +  text = text.replace(/  +\n/g," <br />\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\/!$]("[^"]*"|'[^']*'|[^'">])*>|<!(--.*?--\s*)+>)/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 <a> 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 = "<a href=\"" + url + "\""; +   +  if (title != "") { +    title = title.replace(/"/g,"""); +    title = escapeCharacters(title,"*_"); +    result +=  " title=\"" + title + "\""; +  } +   +  result += ">" + link_text + "</a>"; +   +  return result; +} + + +var _DoImages = function(text) { +// +// Turn Markdown image shortcuts into <img> 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 = "<img src=\"" + url + "\" alt=\"" + alt_text + "\""; + +  // attacklab: Markdown.pl adds empty title attributes to images. +  // Replicate this bug. + +  //if (title != "") { +    title = title.replace(/"/g,"""); +    title = escapeCharacters(title,"*_"); +    result +=  " title=\"" + title + "\""; +  //} +   +  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("<h1>" + _RunSpanGamut(m1) + "</h1>");}); + +  text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm, +    function(matchFound,m1){return hashBlock("<h2>" + _RunSpanGamut(m1) + "</h2>");}); + +  // 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("<h" + h_level + ">" + _RunSpanGamut(m2) + "</h" + h_level + ">"); +    }); + +  return text; +} + +// This declaration keeps Dojo compressor from outputting garbage: +var _ProcessListItems; + +var _DoLists = function(text) { +// +// Form HTML ordered (numbered) and unordered (bulleted) lists. +// + +  // attacklab: add sentinel to hack around khtml/safari bug: +  // http://bugs.webkit.org/show_bug.cgi?id=11231 +  text += "~0"; + +  // Re-usable pattern to match any entirel ul or ol list: + +  /* +    var whole_list = / +    (                 // $1 = whole list +      (               // $2 +        [ ]{0,3}          // attacklab: g_tab_width - 1 +        ([*+-]|\d+[.])        // $3 = first list item marker +        [ \t]+ +      ) +      [^\r]+? +      (               // $4 +        ~0              // sentinel for workaround; should be $ +      | +        \n{2,} +        (?=\S) +        (?!             // Negative lookahead for another list item marker +          [ \t]* +          (?:[*+-]|\d+[.])[ \t]+ +        ) +      ) +    )/g +  */ +  var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm; + +  if (g_list_level) { +    text = text.replace(whole_list,function(wholeMatch,m1,m2) { +      var list = m1; +      var list_type = (m2.search(/[*+-]/g)>-1) ? "ul" : "ol"; + +      // Turn double returns into triple returns, so that we can make a +      // paragraph for the last item in a list, if necessary: +      list = list.replace(/\n{2,}/g,"\n\n\n");; +      var result = _ProcessListItems(list); +   +      // Trim any trailing whitespace, to put the closing `</$list_type>` +      // up on the preceding line, to get it past the current stupid +      // HTML block parser. This is a hack to work around the terrible +      // hack that is the HTML block parser. +      result = result.replace(/\s+$/,""); +      result = "<"+list_type+">" + result + "</"+list_type+">\n"; +      return result; +    }); +  } else { +    whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g; +    text = text.replace(whole_list,function(wholeMatch,m1,m2,m3) { +      var runup = m1; +      var list = m2; + +      var list_type = (m3.search(/[*+-]/g)>-1) ? "ul" : "ol"; +      // Turn double returns into triple returns, so that we can make a +      // paragraph for the last item in a list, if necessary: +      var list = list.replace(/\n{2,}/g,"\n\n\n");; +      var result = _ProcessListItems(list); +      result = runup + "<"+list_type+">\n" + result + "</"+list_type+">\n";  +      return result; +    }); +  } + +  // attacklab: strip sentinel +  text = text.replace(/~0/,""); + +  return text; +} + +_ProcessListItems = function(list_str) { +// +//  Process the contents of a single ordered or unordered list, splitting it +//  into individual list items. +// +  // The $g_list_level global keeps track of when we're inside a list. +  // Each time we enter a list, we increment it; when we leave a list, +  // we decrement. If it's zero, we're not in a list anymore. +  // +  // We do this because when we're not inside a list, we want to treat +  // something like this: +  // +  //    I recommend upgrading to version +  //    8. Oops, now this line is treated +  //    as a sub-list. +  // +  // As a single paragraph, despite the fact that the second line starts +  // with a digit-period-space sequence. +  // +  // Whereas when we're inside a list (or sub-list), that line will be +  // treated as the start of a sub-list. What a kludge, huh? This is +  // an aspect of Markdown's syntax that's hard to parse perfectly +  // without resorting to mind-reading. Perhaps the solution is to +  // change the syntax rules such that sub-lists must start with a +  // starting cardinal number; e.g. "1." or "a.". + +  g_list_level++; + +  // trim trailing blank lines: +  list_str = list_str.replace(/\n{2,}$/,"\n"); + +  // attacklab: add sentinel to emulate \z +  list_str += "~0"; + +  /* +    list_str = list_str.replace(/ +      (\n)?             // leading line = $1 +      (^[ \t]*)           // leading whitespace = $2 +      ([*+-]|\d+[.]) [ \t]+     // list marker = $3 +      ([^\r]+?            // list item text   = $4 +      (\n{1,2})) +      (?= \n* (~0 | \2 ([*+-]|\d+[.]) [ \t]+)) +    /gm, function(){...}); +  */ +  list_str = list_str.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm, +    function(wholeMatch,m1,m2,m3,m4){ +      var item = m4; +      var leading_line = m1; +      var leading_space = m2; + +      if (leading_line || (item.search(/\n{2,}/)>-1)) { +        item = _RunBlockGamut(_Outdent(item)); +      } +      else { +        // Recursion for sub-lists: +        item = _DoLists(_Outdent(item)); +        item = item.replace(/\n$/,""); // chomp(item) +        item = _RunSpanGamut(item); +      } + +      return  "<li>" + item + "</li>\n"; +    } +  ); + +  // attacklab: strip sentinel +  list_str = list_str.replace(/~0/g,""); + +  g_list_level--; +  return list_str; +} + + +var _DoCodeBlocks = function(text) { +// +//  Process Markdown `<pre><code>` 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 = "<pre><code>" + codeblock + "\n</code></pre>"; + +      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 <code></code> 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: +//    +//     <p>Just type <code>foo `bar` baz</code> at the prompt.</p> +//    +//  There's no arbitrary limit to the number of backticks you +//  can use as delimters. If you need three consecutive backticks +//  in your code, use four for delimiters, etc. +// +//  *  You can use spaces to get literal backticks at the edges: +//    +//     ... type `` `bar` `` ... +//    +//     Turns to: +//    +//     ... type <code>`bar`</code> ... +// + +  /* +    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+"<code>"+c+"</code>"; +    }); + +  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,"<"); +  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) { + +  // <strong> must go first: +  text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g, +    "<strong>$2</strong>"); + +  text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g, +    "<em>$2</em>"); + +  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 <pre> content, so we need to fix that: +      bq = bq.replace( +          /(\s*<pre>[^\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("<blockquote>\n" + bq + "\n</blockquote>"); +    }); +  return text; +} + + +var _FormParagraphs = function(text) { +// +//  Params: +//    $text - string to process with html <p> 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 <p> tags. +  // +  var end = grafs.length; +  for (var i=0; i<end; i++) { +    var str = grafs[i]; + +    // if this is an HTML marker, copy it +    if (str.search(/~K(\d+)K/g) >= 0) { +      grafsOut.push(str); +    } +    else if (str.search(/\S/) >= 0) { +      str = _RunSpanGamut(str); +      str = str.replace(/^([ \t]*)/g,"<p>"); +      str += "</p>" +      grafsOut.push(str); +    } + +  } + +  // +  // Unhashify HTML blocks +  // +  end = grafsOut.length; +  for (var i=0; i<end; i++) { +    // if this is a marker for an html block... +    while (grafsOut[i].search(/~K(\d+)K/) >= 0) { +      var blockText = g_html_blocks[RegExp.$1]; +      blockText = blockText.replace(/\$/g,"$$$$"); // Escape any dollar signs +      grafsOut[i] = grafsOut[i].replace(/~K\d+K/,blockText); +    } +  } + +  return grafsOut.join("\n\n"); +} + + +var _EncodeAmpsAndAngles = function(text) { +// Smart processing for ampersands and angle brackets that need to be encoded. +   +  // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin: +  //   http://bumppo.net/projects/amputator/ +  text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&"); +   +  // Encode naked <'s +  text = text.replace(/<(?![a-z\/?\$!])/gi,"<"); +   +  return text; +} + + +var _EncodeBackslashEscapes = function(text) { +// +//   Parameter:  String. +//   Returns: The string, with after processing the following backslash +//         escape sequences. +// + +  // attacklab: The polite way to do this is with the new +  // escapeCharacters() function: +  // +  //  text = escapeCharacters(text,"\\",true); +  //  text = escapeCharacters(text,"`*_{}[]()>#+-.!",true); +  // +  // ...but we're sidestepping its use of the (slow) RegExp constructor +  // as an optimization for Firefox.  This function gets called a LOT. + +  text = text.replace(/\\(\\)/g,escapeCharacters_callback); +  text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g,escapeCharacters_callback); +  return text; +} + + +var _DoAutoLinks = function(text) { + +  text = text.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,"<a href=\"$1\">$1</a>"); + +  // Email addresses: <address@domain.foo> + +  /* +    text = text.replace(/ +      < +      (?:mailto:)? +      ( +        [-.\w]+ +        \@ +        [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+ +      ) +      > +    /gi, _DoAutoLinks_callback()); +  */ +  text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi, +    function(wholeMatch,m1) { +      return _EncodeEmailAddress( _UnescapeSpecialChars(m1) ); +    } +  ); + +  return text; +} + + +var _EncodeEmailAddress = function(addr) { +// +//  Input: an email address, e.g. "foo@example.com" +// +//  Output: the email address as a mailto link, with each character +//  of the address encoded as either a decimal or hex entity, in +//  the hopes of foiling most address harvesting spam bots. E.g.: +// +//  <a href="mailto:foo@e +//     xample.com">foo +//     @example.com</a> +// +//  Based on a filter by Matthew Wickline, posted to the BBEdit-Talk +//  mailing list: <http://tinyurl.com/yu7ue> +// + +  // attacklab: why can't javascript speak hex? +  function char2hex(ch) { +    var hexDigits = '0123456789ABCDEF'; +    var dec = ch.charCodeAt(0); +    return(hexDigits.charAt(dec>>4) + hexDigits.charAt(dec&15)); +  } + +  var encode = [ +    function(ch){return "&#"+ch.charCodeAt(0)+";";}, +    function(ch){return "&#x"+char2hex(ch)+";";}, +    function(ch){return ch;} +  ]; + +  addr = "mailto:" + addr; + +  addr = addr.replace(/./g, function(ch) { +    if (ch == "@") { +        // this *must* be encoded. I insist. +      ch = encode[Math.floor(Math.random()*2)](ch); +    } else if (ch !=":") { +      // leave ':' alone (to spot mailto: later) +      var r = Math.random(); +      // roughly 10% raw, 45% hex, 45% dec +      ch =  ( +          r > .9  ? encode[2](ch)   : +          r > .45 ? encode[1](ch)   : +                encode[0](ch) +        ); +    } +    return ch; +  }); + +  addr = "<a href=\"" + addr + "\">" + addr + "</a>"; +  addr = addr.replace(/">.+:/g,"\">"); // strip the mailto: from the visible part + +  return addr; +} + + +var _UnescapeSpecialChars = function(text) { +// +// Swap back in all the special characters we've hidden. +// +  text = text.replace(/~E(\d+)E/g, +    function(wholeMatch,m1) { +      var charCodeToReplace = parseInt(m1); +      return String.fromCharCode(charCodeToReplace); +    } +  ); +  return text; +} + + +var _Outdent = function(text) { +// +// Remove one level of line-leading tabs or spaces +// + +  // attacklab: hack around Konqueror 3.5.4 bug: +  // "----------bug".replace(/^-/g,"") == "bug" + +  text = text.replace(/^(\t|[ ]{1,4})/gm,"~0"); // attacklab: g_tab_width + +  // attacklab: clean up hack +  text = text.replace(/~0/g,"") + +  return text; +} + +var _Detab = function(text) { +// attacklab: Detab's completely rewritten for speed. +// In perl we could fix it by anchoring the regexp with \G. +// In javascript we're less fortunate. + +  // expand first n-1 tabs +  text = text.replace(/\t(?=\t)/g,"    "); // attacklab: g_tab_width + +  // replace the nth with two sentinels +  text = text.replace(/\t/g,"~A~B"); + +  // use the sentinel to anchor our regex so it doesn't explode +  text = text.replace(/~B(.+?)~A/g, +    function(wholeMatch,m1,m2) { +      var leadingText = m1; +      var numSpaces = 4 - leadingText.length % 4;  // attacklab: g_tab_width + +      // there *must* be a better way to do this: +      for (var i=0; i<numSpaces; i++) leadingText+=" "; + +      return leadingText; +    } +  ); + +  // clean up sentinels +  text = text.replace(/~A/g,"    ");  // attacklab: g_tab_width +  text = text.replace(/~B/g,""); + +  return text; +} + + +// +//  attacklab: Utility functions +// + + +var escapeCharacters = function(text, charsToEscape, afterBackslash) { +  // First we have to escape the escape characters so that +  // we can build a character class out of them +  var regexString = "([" + charsToEscape.replace(/([\[\]\\])/g,"\\$1") + "])"; + +  if (afterBackslash) { +    regexString = "\\\\" + regexString; +  } + +  var regex = new RegExp(regexString,"g"); +  text = text.replace(regex,escapeCharacters_callback); + +  return text; +} + + +var escapeCharacters_callback = function(wholeMatch,m1) { +  var charCodeToEscape = m1.charCodeAt(0); +  return "~E"+charCodeToEscape+"E"; +} + +} // end of Showdown.converter
\ No newline at end of file  | 
