Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Showdown.js

  • Loading branch information...
commit 2c53536f5ca45ac4e74b7feb48f0698b9e7cee71 1 parent 410fd40
James Wang authored March 01, 2012

Showing 1 changed file with 1,362 additions and 0 deletions. Show diff stats Hide diff stats

  1. 1,362  vendor/showdown.js
1,362  vendor/showdown.js
... ...
@@ -0,0 +1,1362 @@
  1
+//
  2
+// showdown.js -- A javascript port of Markdown.
  3
+//
  4
+// Copyright (c) 2007 John Fraser.
  5
+//
  6
+// Original Markdown Copyright (c) 2004-2005 John Gruber
  7
+//   <http://daringfireball.net/projects/markdown/>
  8
+//
  9
+// Redistributable under a BSD-style open source license.
  10
+// See license.txt for more information.
  11
+//
  12
+// The full source distribution is at:
  13
+//
  14
+//				A A L
  15
+//				T C A
  16
+//				T K B
  17
+//
  18
+//   <http://www.attacklab.net/>
  19
+//
  20
+
  21
+//
  22
+// Wherever possible, Showdown is a straight, line-by-line port
  23
+// of the Perl version of Markdown.
  24
+//
  25
+// This is not a normal parser design; it's basically just a
  26
+// series of string substitutions.  It's hard to read and
  27
+// maintain this way,  but keeping Showdown close to the original
  28
+// design makes it easier to port new features.
  29
+//
  30
+// More importantly, Showdown behaves like markdown.pl in most
  31
+// edge cases.  So web applications can do client-side preview
  32
+// in Javascript, and then build identical HTML on the server.
  33
+//
  34
+// This port needs the new RegExp functionality of ECMA 262,
  35
+// 3rd Edition (i.e. Javascript 1.5).  Most modern web browsers
  36
+// should do fine.  Even with the new regular expression features,
  37
+// We do a lot of work to emulate Perl's regex functionality.
  38
+// The tricky changes in this file mostly have the "attacklab:"
  39
+// label.  Major or self-explanatory changes don't.
  40
+//
  41
+// Smart diff tools like Araxis Merge will be able to match up
  42
+// this file with markdown.pl in a useful way.  A little tweaking
  43
+// helps: in a copy of markdown.pl, replace "#" with "//" and
  44
+// replace "$text" with "text".  Be sure to ignore whitespace
  45
+// and line endings.
  46
+//
  47
+
  48
+
  49
+//
  50
+// Showdown usage:
  51
+//
  52
+//   var text = "Markdown *rocks*.";
  53
+//
  54
+//   var converter = new Showdown.converter();
  55
+//   var html = converter.makeHtml(text);
  56
+//
  57
+//   alert(html);
  58
+//
  59
+// Note: move the sample code to the bottom of this
  60
+// file before uncommenting it.
  61
+//
  62
+
  63
+
  64
+// **************************************************
  65
+// GitHub Flavored Markdown modifications by Tekkub
  66
+// http://github.github.com/github-flavored-markdown/
  67
+//
  68
+// Modifications are tagged with "GFM"
  69
+// **************************************************
  70
+
  71
+//
  72
+// Showdown namespace
  73
+//
  74
+var Showdown = {};
  75
+
  76
+//
  77
+// converter
  78
+//
  79
+// Wraps all "globals" so that the only thing
  80
+// exposed is makeHtml().
  81
+//
  82
+Showdown.converter = function() {
  83
+
  84
+//
  85
+// Globals:
  86
+//
  87
+
  88
+// Global hashes, used by various utility routines
  89
+var g_urls;
  90
+var g_titles;
  91
+var g_html_blocks;
  92
+
  93
+// Used to track when we're inside an ordered or unordered list
  94
+// (see _ProcessListItems() for details):
  95
+var g_list_level = 0;
  96
+
  97
+
  98
+this.makeHtml = function(text) {
  99
+//
  100
+// Main function. The order in which other subs are called here is
  101
+// essential. Link and image substitutions need to happen before
  102
+// _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the <a>
  103
+// and <img> tags get encoded.
  104
+//
  105
+
  106
+	// Clear the global hashes. If we don't clear these, you get conflicts
  107
+	// from other articles when generating a page which contains more than
  108
+	// one article (e.g. an index page that shows the N most recent
  109
+	// articles):
  110
+	g_urls = new Array();
  111
+	g_titles = new Array();
  112
+	g_html_blocks = new Array();
  113
+
  114
+	// attacklab: Replace ~ with ~T
  115
+	// This lets us use tilde as an escape char to avoid md5 hashes
  116
+	// The choice of character is arbitray; anything that isn't
  117
+    // magic in Markdown will work.
  118
+	text = text.replace(/~/g,"~T");
  119
+
  120
+	// attacklab: Replace $ with ~D
  121
+	// RegExp interprets $ as a special character
  122
+	// when it's in a replacement string
  123
+	text = text.replace(/\$/g,"~D");
  124
+
  125
+	// Standardize line endings
  126
+	text = text.replace(/\r\n/g,"\n"); // DOS to Unix
  127
+	text = text.replace(/\r/g,"\n"); // Mac to Unix
  128
+
  129
+	// Make sure text begins and ends with a couple of newlines:
  130
+	text = "\n\n" + text + "\n\n";
  131
+
  132
+	// Convert all tabs to spaces.
  133
+	text = _Detab(text);
  134
+
  135
+	// Strip any lines consisting only of spaces and tabs.
  136
+	// This makes subsequent regexen easier to write, because we can
  137
+	// match consecutive blank lines with /\n+/ instead of something
  138
+	// contorted like /[ \t]*\n+/ .
  139
+	text = text.replace(/^[ \t]+$/mg,"");
  140
+
  141
+	// Turn block-level HTML blocks into hash entries
  142
+	text = _HashHTMLBlocks(text);
  143
+
  144
+	// Strip link definitions, store in hashes.
  145
+	text = _StripLinkDefinitions(text);
  146
+
  147
+	text = _RunBlockGamut(text);
  148
+
  149
+	text = _UnescapeSpecialChars(text);
  150
+
  151
+	// attacklab: Restore dollar signs
  152
+	text = text.replace(/~D/g,"$$");
  153
+
  154
+	// attacklab: Restore tildes
  155
+	text = text.replace(/~T/g,"~");
  156
+
  157
+  // ** GFM **  Auto-link URLs and emails
  158
+  text = text.replace(/https?\:\/\/[^"\s\<\>]*[^.,;'">\:\s\<\>\)\]\!]/g, function(wholeMatch,matchIndex){
  159
+    var left = text.slice(0, matchIndex), right = text.slice(matchIndex)
  160
+    if (left.match(/<[^>]+$/) && right.match(/^[^>]*>/)) {return wholeMatch}
  161
+    href = wholeMatch.replace(/^http:\/\/github.com\//, "https://github.com/")
  162
+    return "<a href='" + href + "'>" + wholeMatch + "</a>";
  163
+  });
  164
+  text = text.replace(/[a-z0-9_\-+=.]+@[a-z0-9\-]+(\.[a-z0-9-]+)+/ig, function(wholeMatch){return "<a href='mailto:" + wholeMatch + "'>" + wholeMatch + "</a>";});
  165
+
  166
+  // ** GFM ** Auto-link sha1 if GitHub.nameWithOwner is defined
  167
+  text = text.replace(/[a-f0-9]{40}/ig, function(wholeMatch,matchIndex){
  168
+    if (typeof(GitHub) == "undefined" || typeof(GitHub.nameWithOwner) == "undefined") {return wholeMatch;}
  169
+    var left = text.slice(0, matchIndex), right = text.slice(matchIndex)
  170
+    if (left.match(/@$/) || (left.match(/<[^>]+$/) && right.match(/^[^>]*>/))) {return wholeMatch;}
  171
+    return "<a href='http://github.com/" + GitHub.nameWithOwner + "/commit/" + wholeMatch + "'>" + wholeMatch.substring(0,7) + "</a>";
  172
+  });
  173
+
  174
+  // ** GFM ** Auto-link user@sha1 if GitHub.nameWithOwner is defined
  175
+  text = text.replace(/([a-z0-9_\-+=.]+)@([a-f0-9]{40})/ig, function(wholeMatch,username,sha,matchIndex){
  176
+    if (typeof(GitHub) == "undefined" || typeof(GitHub.nameWithOwner) == "undefined") {return wholeMatch;}
  177
+    GitHub.repoName = GitHub.repoName || _GetRepoName()
  178
+    var left = text.slice(0, matchIndex), right = text.slice(matchIndex)
  179
+    if (left.match(/\/$/) || (left.match(/<[^>]+$/) && right.match(/^[^>]*>/))) {return wholeMatch;}
  180
+    return "<a href='http://github.com/" + username + "/" + GitHub.repoName + "/commit/" + sha + "'>" + username + "@" + sha.substring(0,7) + "</a>";
  181
+  });
  182
+
  183
+  // ** GFM ** Auto-link user/repo@sha1
  184
+  text = text.replace(/([a-z0-9_\-+=.]+\/[a-z0-9_\-+=.]+)@([a-f0-9]{40})/ig, function(wholeMatch,repo,sha){
  185
+    return "<a href='http://github.com/" + repo + "/commit/" + sha + "'>" + repo + "@" + sha.substring(0,7) + "</a>";
  186
+  });
  187
+
  188
+  // ** GFM ** Auto-link #issue if GitHub.nameWithOwner is defined
  189
+  text = text.replace(/#([0-9]+)/ig, function(wholeMatch,issue,matchIndex){
  190
+    if (typeof(GitHub) == "undefined" || typeof(GitHub.nameWithOwner) == "undefined") {return wholeMatch;}
  191
+    var left = text.slice(0, matchIndex), right = text.slice(matchIndex)
  192
+    if (left == "" || left.match(/[a-z0-9_\-+=.]$/) || (left.match(/<[^>]+$/) && right.match(/^[^>]*>/))) {return wholeMatch;}
  193
+    return "<a href='http://github.com/" + GitHub.nameWithOwner + "/issues/#issue/" + issue + "'>" + wholeMatch + "</a>";
  194
+  });
  195
+
  196
+  // ** GFM ** Auto-link user#issue if GitHub.nameWithOwner is defined
  197
+  text = text.replace(/([a-z0-9_\-+=.]+)#([0-9]+)/ig, function(wholeMatch,username,issue,matchIndex){
  198
+    if (typeof(GitHub) == "undefined" || typeof(GitHub.nameWithOwner) == "undefined") {return wholeMatch;}
  199
+    GitHub.repoName = GitHub.repoName || _GetRepoName()
  200
+    var left = text.slice(0, matchIndex), right = text.slice(matchIndex)
  201
+    if (left.match(/\/$/) || (left.match(/<[^>]+$/) && right.match(/^[^>]*>/))) {return wholeMatch;}
  202
+    return "<a href='http://github.com/" + username + "/" + GitHub.repoName + "/issues/#issue/" + issue + "'>" + wholeMatch + "</a>";
  203
+  });
  204
+
  205
+  // ** GFM ** Auto-link user/repo#issue
  206
+  text = text.replace(/([a-z0-9_\-+=.]+\/[a-z0-9_\-+=.]+)#([0-9]+)/ig, function(wholeMatch,repo,issue){
  207
+    return "<a href='http://github.com/" + repo + "/issues/#issue/" + issue + "'>" + wholeMatch + "</a>";
  208
+  });
  209
+
  210
+	return text;
  211
+}
  212
+
  213
+
  214
+var _GetRepoName = function() {
  215
+  return GitHub.nameWithOwner.match(/^.+\/(.+)$/)[1]
  216
+}
  217
+
  218
+var _StripLinkDefinitions = function(text) {
  219
+//
  220
+// Strips link definitions from text, stores the URLs and titles in
  221
+// hash references.
  222
+//
  223
+
  224
+	// Link defs are in the form: ^[id]: url "optional title"
  225
+
  226
+	/*
  227
+		var text = text.replace(/
  228
+				^[ ]{0,3}\[(.+)\]:  // id = $1  attacklab: g_tab_width - 1
  229
+				  [ \t]*
  230
+				  \n?				// maybe *one* newline
  231
+				  [ \t]*
  232
+				<?(\S+?)>?			// url = $2
  233
+				  [ \t]*
  234
+				  \n?				// maybe one newline
  235
+				  [ \t]*
  236
+				(?:
  237
+				  (\n*)				// any lines skipped = $3 attacklab: lookbehind removed
  238
+				  ["(]
  239
+				  (.+?)				// title = $4
  240
+				  [")]
  241
+				  [ \t]*
  242
+				)?					// title is optional
  243
+				(?:\n+|$)
  244
+			  /gm,
  245
+			  function(){...});
  246
+	*/
  247
+	var text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm,
  248
+		function (wholeMatch,m1,m2,m3,m4) {
  249
+			m1 = m1.toLowerCase();
  250
+			g_urls[m1] = _EncodeAmpsAndAngles(m2);  // Link IDs are case-insensitive
  251
+			if (m3) {
  252
+				// Oops, found blank lines, so it's not a title.
  253
+				// Put back the parenthetical statement we stole.
  254
+				return m3+m4;
  255
+			} else if (m4) {
  256
+				g_titles[m1] = m4.replace(/"/g,"&quot;");
  257
+			}
  258
+
  259
+			// Completely remove the definition from the text
  260
+			return "";
  261
+		}
  262
+	);
  263
+
  264
+	return text;
  265
+}
  266
+
  267
+
  268
+var _HashHTMLBlocks = function(text) {
  269
+	// attacklab: Double up blank lines to reduce lookaround
  270
+	text = text.replace(/\n/g,"\n\n");
  271
+
  272
+	// Hashify HTML blocks:
  273
+	// We only want to do this for block-level HTML tags, such as headers,
  274
+	// lists, and tables. That's because we still want to wrap <p>s around
  275
+	// "paragraphs" that are wrapped in non-block-level tags, such as anchors,
  276
+	// phrase emphasis, and spans. The list of tags we're looking for is
  277
+	// hard-coded:
  278
+	var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del"
  279
+	var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math"
  280
+
  281
+	// First, look for nested blocks, e.g.:
  282
+	//   <div>
  283
+	//     <div>
  284
+	//     tags for inner block must be indented.
  285
+	//     </div>
  286
+	//   </div>
  287
+	//
  288
+	// The outermost tags must start at the left margin for this to match, and
  289
+	// the inner nested divs must be indented.
  290
+	// We need to do this before the next, more liberal match, because the next
  291
+	// match will start at the first `<div>` and stop at the first `</div>`.
  292
+
  293
+	// attacklab: This regex can be expensive when it fails.
  294
+	/*
  295
+		var text = text.replace(/
  296
+		(						// save in $1
  297
+			^					// start of line  (with /m)
  298
+			<($block_tags_a)	// start tag = $2
  299
+			\b					// word break
  300
+								// attacklab: hack around khtml/pcre bug...
  301
+			[^\r]*?\n			// any number of lines, minimally matching
  302
+			</\2>				// the matching end tag
  303
+			[ \t]*				// trailing spaces/tabs
  304
+			(?=\n+)				// followed by a newline
  305
+		)						// attacklab: there are sentinel newlines at end of document
  306
+		/gm,function(){...}};
  307
+	*/
  308
+	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);
  309
+
  310
+	//
  311
+	// Now match more liberally, simply from `\n<tag>` to `</tag>\n`
  312
+	//
  313
+
  314
+	/*
  315
+		var text = text.replace(/
  316
+		(						// save in $1
  317
+			^					// start of line  (with /m)
  318
+			<($block_tags_b)	// start tag = $2
  319
+			\b					// word break
  320
+								// attacklab: hack around khtml/pcre bug...
  321
+			[^\r]*?				// any number of lines, minimally matching
  322
+			.*</\2>				// the matching end tag
  323
+			[ \t]*				// trailing spaces/tabs
  324
+			(?=\n+)				// followed by a newline
  325
+		)						// attacklab: there are sentinel newlines at end of document
  326
+		/gm,function(){...}};
  327
+	*/
  328
+	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);
  329
+
  330
+	// Special case just for <hr />. It was easier to make a special case than
  331
+	// to make the other regex more complicated.
  332
+
  333
+	/*
  334
+		text = text.replace(/
  335
+		(						// save in $1
  336
+			\n\n				// Starting after a blank line
  337
+			[ ]{0,3}
  338
+			(<(hr)				// start tag = $2
  339
+			\b					// word break
  340
+			([^<>])*?			//
  341
+			\/?>)				// the matching end tag
  342
+			[ \t]*
  343
+			(?=\n{2,})			// followed by a blank line
  344
+		)
  345
+		/g,hashElement);
  346
+	*/
  347
+	text = text.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,hashElement);
  348
+
  349
+	// Special case for standalone HTML comments:
  350
+
  351
+	/*
  352
+		text = text.replace(/
  353
+		(						// save in $1
  354
+			\n\n				// Starting after a blank line
  355
+			[ ]{0,3}			// attacklab: g_tab_width - 1
  356
+			<!
  357
+			(--[^\r]*?--\s*)+
  358
+			>
  359
+			[ \t]*
  360
+			(?=\n{2,})			// followed by a blank line
  361
+		)
  362
+		/g,hashElement);
  363
+	*/
  364
+	text = text.replace(/(\n\n[ ]{0,3}<!(--[^\r]*?--\s*)+>[ \t]*(?=\n{2,}))/g,hashElement);
  365
+
  366
+	// PHP and ASP-style processor instructions (<?...?> and <%...%>)
  367
+
  368
+	/*
  369
+		text = text.replace(/
  370
+		(?:
  371
+			\n\n				// Starting after a blank line
  372
+		)
  373
+		(						// save in $1
  374
+			[ ]{0,3}			// attacklab: g_tab_width - 1
  375
+			(?:
  376
+				<([?%])			// $2
  377
+				[^\r]*?
  378
+				\2>
  379
+			)
  380
+			[ \t]*
  381
+			(?=\n{2,})			// followed by a blank line
  382
+		)
  383
+		/g,hashElement);
  384
+	*/
  385
+	text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,hashElement);
  386
+
  387
+	// attacklab: Undo double lines (see comment at top of this function)
  388
+	text = text.replace(/\n\n/g,"\n");
  389
+	return text;
  390
+}
  391
+
  392
+var hashElement = function(wholeMatch,m1) {
  393
+	var blockText = m1;
  394
+
  395
+	// Undo double lines
  396
+	blockText = blockText.replace(/\n\n/g,"\n");
  397
+	blockText = blockText.replace(/^\n/,"");
  398
+
  399
+	// strip trailing blank lines
  400
+	blockText = blockText.replace(/\n+$/g,"");
  401
+
  402
+	// Replace the element text with a marker ("~KxK" where x is its key)
  403
+	blockText = "\n\n~K" + (g_html_blocks.push(blockText)-1) + "K\n\n";
  404
+
  405
+	return blockText;
  406
+};
  407
+
  408
+var _RunBlockGamut = function(text) {
  409
+//
  410
+// These are all the transformations that form block-level
  411
+// tags like paragraphs, headers, and list items.
  412
+//
  413
+	text = _DoHeaders(text);
  414
+
  415
+	// Do Horizontal Rules:
  416
+	var key = hashBlock("<hr />");
  417
+	text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm,key);
  418
+	text = text.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm,key);
  419
+	text = text.replace(/^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$/gm,key);
  420
+
  421
+	text = _DoLists(text);
  422
+	text = _DoCodeBlocks(text);
  423
+	text = _DoBlockQuotes(text);
  424
+
  425
+	// We already ran _HashHTMLBlocks() before, in Markdown(), but that
  426
+	// was to escape raw HTML in the original Markdown source. This time,
  427
+	// we're escaping the markup we've just created, so that we don't wrap
  428
+	// <p> tags around block-level tags.
  429
+	text = _HashHTMLBlocks(text);
  430
+	text = _FormParagraphs(text);
  431
+
  432
+	return text;
  433
+}
  434
+
  435
+
  436
+var _RunSpanGamut = function(text) {
  437
+//
  438
+// These are all the transformations that occur *within* block-level
  439
+// tags like paragraphs, headers, and list items.
  440
+//
  441
+
  442
+	text = _DoCodeSpans(text);
  443
+	text = _EscapeSpecialCharsWithinTagAttributes(text);
  444
+	text = _EncodeBackslashEscapes(text);
  445
+
  446
+	// Process anchor and image tags. Images must come first,
  447
+	// because ![foo][f] looks like an anchor.
  448
+	text = _DoImages(text);
  449
+	text = _DoAnchors(text);
  450
+
  451
+	// Make links out of things like `<http://example.com/>`
  452
+	// Must come after _DoAnchors(), because you can use < and >
  453
+	// delimiters in inline links like [this](<url>).
  454
+	text = _DoAutoLinks(text);
  455
+	text = _EncodeAmpsAndAngles(text);
  456
+	text = _DoItalicsAndBold(text);
  457
+
  458
+	// Do hard breaks:
  459
+	text = text.replace(/  +\n/g," <br />\n");
  460
+
  461
+	return text;
  462
+}
  463
+
  464
+var _EscapeSpecialCharsWithinTagAttributes = function(text) {
  465
+//
  466
+// Within tags -- meaning between < and > -- encode [\ ` * _] so they
  467
+// don't conflict with their use in Markdown for code, italics and strong.
  468
+//
  469
+
  470
+	// Build a regex to find HTML tags and comments.  See Friedl's
  471
+	// "Mastering Regular Expressions", 2nd Ed., pp. 200-201.
  472
+	var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|<!(--.*?--\s*)+>)/gi;
  473
+
  474
+	text = text.replace(regex, function(wholeMatch) {
  475
+		var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g,"$1`");
  476
+		tag = escapeCharacters(tag,"\\`*_");
  477
+		return tag;
  478
+	});
  479
+
  480
+	return text;
  481
+}
  482
+
  483
+var _DoAnchors = function(text) {
  484
+//
  485
+// Turn Markdown link shortcuts into XHTML <a> tags.
  486
+//
  487
+	//
  488
+	// First, handle reference-style links: [link text] [id]
  489
+	//
  490
+
  491
+	/*
  492
+		text = text.replace(/
  493
+		(							// wrap whole match in $1
  494
+			\[
  495
+			(
  496
+				(?:
  497
+					\[[^\]]*\]		// allow brackets nested one level
  498
+					|
  499
+					[^\[]			// or anything else
  500
+				)*
  501
+			)
  502
+			\]
  503
+
  504
+			[ ]?					// one optional space
  505
+			(?:\n[ ]*)?				// one optional newline followed by spaces
  506
+
  507
+			\[
  508
+			(.*?)					// id = $3
  509
+			\]
  510
+		)()()()()					// pad remaining backreferences
  511
+		/g,_DoAnchors_callback);
  512
+	*/
  513
+	text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeAnchorTag);
  514
+
  515
+	//
  516
+	// Next, inline-style links: [link text](url "optional title")
  517
+	//
  518
+
  519
+	/*
  520
+		text = text.replace(/
  521
+			(						// wrap whole match in $1
  522
+				\[
  523
+				(
  524
+					(?:
  525
+						\[[^\]]*\]	// allow brackets nested one level
  526
+					|
  527
+					[^\[\]]			// or anything else
  528
+				)
  529
+			)
  530
+			\]
  531
+			\(						// literal paren
  532
+			[ \t]*
  533
+			()						// no id, so leave $3 empty
  534
+			<?(.*?)>?				// href = $4
  535
+			[ \t]*
  536
+			(						// $5
  537
+				(['"])				// quote char = $6
  538
+				(.*?)				// Title = $7
  539
+				\6					// matching quote
  540
+				[ \t]*				// ignore any spaces/tabs between closing quote and )
  541
+			)?						// title is optional
  542
+			\)
  543
+		)
  544
+		/g,writeAnchorTag);
  545
+	*/
  546
+	text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()<?(.*?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeAnchorTag);
  547
+
  548
+	//
  549
+	// Last, handle reference-style shortcuts: [link text]
  550
+	// These must come last in case you've also got [link test][1]
  551
+	// or [link test](/foo)
  552
+	//
  553
+
  554
+	/*
  555
+		text = text.replace(/
  556
+		(		 					// wrap whole match in $1
  557
+			\[
  558
+			([^\[\]]+)				// link text = $2; can't contain '[' or ']'
  559
+			\]
  560
+		)()()()()()					// pad rest of backreferences
  561
+		/g, writeAnchorTag);
  562
+	*/
  563
+	text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag);
  564
+
  565
+	return text;
  566
+}
  567
+
  568
+var writeAnchorTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) {
  569
+	if (m7 == undefined) m7 = "";
  570
+	var whole_match = m1;
  571
+	var link_text   = m2;
  572
+	var link_id	 = m3.toLowerCase();
  573
+	var url		= m4;
  574
+	var title	= m7;
  575
+
  576
+	if (url == "") {
  577
+		if (link_id == "") {
  578
+			// lower-case and turn embedded newlines into spaces
  579
+			link_id = link_text.toLowerCase().replace(/ ?\n/g," ");
  580
+		}
  581
+		url = "#"+link_id;
  582
+
  583
+		if (g_urls[link_id] != undefined) {
  584
+			url = g_urls[link_id];
  585
+			if (g_titles[link_id] != undefined) {
  586
+				title = g_titles[link_id];
  587
+			}
  588
+		}
  589
+		else {
  590
+			if (whole_match.search(/\(\s*\)$/m)>-1) {
  591
+				// Special case for explicit empty url
  592
+				url = "";
  593
+			} else {
  594
+				return whole_match;
  595
+			}
  596
+		}
  597
+	}
  598
+
  599
+	url = escapeCharacters(url,"*_");
  600
+	var result = "<a href=\"" + url + "\"";
  601
+
  602
+	if (title != "") {
  603
+		title = title.replace(/"/g,"&quot;");
  604
+		title = escapeCharacters(title,"*_");
  605
+		result +=  " title=\"" + title + "\"";
  606
+	}
  607
+
  608
+	result += ">" + link_text + "</a>";
  609
+
  610
+	return result;
  611
+}
  612
+
  613
+
  614
+var _DoImages = function(text) {
  615
+//
  616
+// Turn Markdown image shortcuts into <img> tags.
  617
+//
  618
+
  619
+	//
  620
+	// First, handle reference-style labeled images: ![alt text][id]
  621
+	//
  622
+
  623
+	/*
  624
+		text = text.replace(/
  625
+		(						// wrap whole match in $1
  626
+			!\[
  627
+			(.*?)				// alt text = $2
  628
+			\]
  629
+
  630
+			[ ]?				// one optional space
  631
+			(?:\n[ ]*)?			// one optional newline followed by spaces
  632
+
  633
+			\[
  634
+			(.*?)				// id = $3
  635
+			\]
  636
+		)()()()()				// pad rest of backreferences
  637
+		/g,writeImageTag);
  638
+	*/
  639
+	text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeImageTag);
  640
+
  641
+	//
  642
+	// Next, handle inline images:  ![alt text](url "optional title")
  643
+	// Don't forget: encode * and _
  644
+
  645
+	/*
  646
+		text = text.replace(/
  647
+		(						// wrap whole match in $1
  648
+			!\[
  649
+			(.*?)				// alt text = $2
  650
+			\]
  651
+			\s?					// One optional whitespace character
  652
+			\(					// literal paren
  653
+			[ \t]*
  654
+			()					// no id, so leave $3 empty
  655
+			<?(\S+?)>?			// src url = $4
  656
+			[ \t]*
  657
+			(					// $5
  658
+				(['"])			// quote char = $6
  659
+				(.*?)			// title = $7
  660
+				\6				// matching quote
  661
+				[ \t]*
  662
+			)?					// title is optional
  663
+		\)
  664
+		)
  665
+		/g,writeImageTag);
  666
+	*/
  667
+	text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()<?(\S+?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeImageTag);
  668
+
  669
+	return text;
  670
+}
  671
+
  672
+var writeImageTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) {
  673
+	var whole_match = m1;
  674
+	var alt_text   = m2;
  675
+	var link_id	 = m3.toLowerCase();
  676
+	var url		= m4;
  677
+	var title	= m7;
  678
+
  679
+	if (!title) title = "";
  680
+
  681
+	if (url == "") {
  682
+		if (link_id == "") {
  683
+			// lower-case and turn embedded newlines into spaces
  684
+			link_id = alt_text.toLowerCase().replace(/ ?\n/g," ");
  685
+		}
  686
+		url = "#"+link_id;
  687
+
  688
+		if (g_urls[link_id] != undefined) {
  689
+			url = g_urls[link_id];
  690
+			if (g_titles[link_id] != undefined) {
  691
+				title = g_titles[link_id];
  692
+			}
  693
+		}
  694
+		else {
  695
+			return whole_match;
  696
+		}
  697
+	}
  698
+
  699
+	alt_text = alt_text.replace(/"/g,"&quot;");
  700
+	url = escapeCharacters(url,"*_");
  701
+	var result = "<img src=\"" + url + "\" alt=\"" + alt_text + "\"";
  702
+
  703
+	// attacklab: Markdown.pl adds empty title attributes to images.
  704
+	// Replicate this bug.
  705
+
  706
+	//if (title != "") {
  707
+		title = title.replace(/"/g,"&quot;");
  708
+		title = escapeCharacters(title,"*_");
  709
+		result +=  " title=\"" + title + "\"";
  710
+	//}
  711
+
  712
+	result += " />";
  713
+
  714
+	return result;
  715
+}
  716
+
  717
+
  718
+var _DoHeaders = function(text) {
  719
+
  720
+	// Setext-style headers:
  721
+	//	Header 1
  722
+	//	========
  723
+	//
  724
+	//	Header 2
  725
+	//	--------
  726
+	//
  727
+	text = text.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm,
  728
+		function(wholeMatch,m1){return hashBlock("<h1>" + _RunSpanGamut(m1) + "</h1>");});
  729
+
  730
+	text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,
  731
+		function(matchFound,m1){return hashBlock("<h2>" + _RunSpanGamut(m1) + "</h2>");});
  732
+
  733
+	// atx-style headers:
  734
+	//  # Header 1
  735
+	//  ## Header 2
  736
+	//  ## Header 2 with closing hashes ##
  737
+	//  ...
  738
+	//  ###### Header 6
  739
+	//
  740
+
  741
+	/*
  742
+		text = text.replace(/
  743
+			^(\#{1,6})				// $1 = string of #'s
  744
+			[ \t]*
  745
+			(.+?)					// $2 = Header text
  746
+			[ \t]*
  747
+			\#*						// optional closing #'s (not counted)
  748
+			\n+
  749
+		/gm, function() {...});
  750
+	*/
  751
+
  752
+	text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,
  753
+		function(wholeMatch,m1,m2) {
  754
+			var h_level = m1.length;
  755
+			return hashBlock("<h" + h_level + ">" + _RunSpanGamut(m2) + "</h" + h_level + ">");
  756
+		});
  757
+
  758
+	return text;
  759
+}
  760
+
  761
+// This declaration keeps Dojo compressor from outputting garbage:
  762
+var _ProcessListItems;
  763
+
  764
+var _DoLists = function(text) {
  765
+//
  766
+// Form HTML ordered (numbered) and unordered (bulleted) lists.
  767
+//
  768
+
  769
+	// attacklab: add sentinel to hack around khtml/safari bug:
  770
+	// http://bugs.webkit.org/show_bug.cgi?id=11231
  771
+	text += "~0";
  772
+
  773
+	// Re-usable pattern to match any entirel ul or ol list:
  774
+
  775
+	/*
  776
+		var whole_list = /
  777
+		(									// $1 = whole list
  778
+			(								// $2
  779
+				[ ]{0,3}					// attacklab: g_tab_width - 1
  780
+				([*+-]|\d+[.])				// $3 = first list item marker
  781
+				[ \t]+
  782
+			)
  783
+			[^\r]+?
  784
+			(								// $4
  785
+				~0							// sentinel for workaround; should be $
  786
+			|
  787
+				\n{2,}
  788
+				(?=\S)
  789
+				(?!							// Negative lookahead for another list item marker
  790
+					[ \t]*
  791
+					(?:[*+-]|\d+[.])[ \t]+
  792
+				)
  793
+			)
  794
+		)/g
  795
+	*/
  796
+	var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
  797
+
  798
+	if (g_list_level) {
  799
+		text = text.replace(whole_list,function(wholeMatch,m1,m2) {
  800
+			var list = m1;
  801
+			var list_type = (m2.search(/[*+-]/g)>-1) ? "ul" : "ol";
  802
+
  803
+			// Turn double returns into triple returns, so that we can make a
  804
+			// paragraph for the last item in a list, if necessary:
  805
+			list = list.replace(/\n{2,}/g,"\n\n\n");;
  806
+			var result = _ProcessListItems(list);
  807
+
  808
+			// Trim any trailing whitespace, to put the closing `</$list_type>`
  809
+			// up on the preceding line, to get it past the current stupid
  810
+			// HTML block parser. This is a hack to work around the terrible
  811
+			// hack that is the HTML block parser.
  812
+			result = result.replace(/\s+$/,"");
  813
+			result = "<"+list_type+">" + result + "</"+list_type+">\n";
  814
+			return result;
  815
+		});
  816
+	} else {
  817
+		whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g;
  818
+		text = text.replace(whole_list,function(wholeMatch,m1,m2,m3) {
  819
+			var runup = m1;
  820
+			var list = m2;
  821
+
  822
+			var list_type = (m3.search(/[*+-]/g)>-1) ? "ul" : "ol";
  823
+			// Turn double returns into triple returns, so that we can make a
  824
+			// paragraph for the last item in a list, if necessary:
  825
+			var list = list.replace(/\n{2,}/g,"\n\n\n");;
  826
+			var result = _ProcessListItems(list);
  827
+			result = runup + "<"+list_type+">\n" + result + "</"+list_type+">\n";
  828
+			return result;
  829
+		});
  830
+	}
  831
+
  832
+	// attacklab: strip sentinel
  833
+	text = text.replace(/~0/,"");
  834
+
  835
+	return text;
  836
+}
  837
+
  838
+_ProcessListItems = function(list_str) {
  839
+//
  840
+//  Process the contents of a single ordered or unordered list, splitting it
  841
+//  into individual list items.
  842
+//
  843
+	// The $g_list_level global keeps track of when we're inside a list.
  844
+	// Each time we enter a list, we increment it; when we leave a list,
  845
+	// we decrement. If it's zero, we're not in a list anymore.
  846
+	//
  847
+	// We do this because when we're not inside a list, we want to treat
  848
+	// something like this:
  849
+	//
  850
+	//    I recommend upgrading to version
  851
+	//    8. Oops, now this line is treated
  852
+	//    as a sub-list.
  853
+	//
  854
+	// As a single paragraph, despite the fact that the second line starts
  855
+	// with a digit-period-space sequence.
  856
+	//
  857
+	// Whereas when we're inside a list (or sub-list), that line will be
  858
+	// treated as the start of a sub-list. What a kludge, huh? This is
  859
+	// an aspect of Markdown's syntax that's hard to parse perfectly
  860
+	// without resorting to mind-reading. Perhaps the solution is to
  861
+	// change the syntax rules such that sub-lists must start with a
  862
+	// starting cardinal number; e.g. "1." or "a.".
  863
+
  864
+	g_list_level++;
  865
+
  866
+	// trim trailing blank lines:
  867
+	list_str = list_str.replace(/\n{2,}$/,"\n");
  868
+
  869
+	// attacklab: add sentinel to emulate \z
  870
+	list_str += "~0";
  871
+
  872
+	/*
  873
+		list_str = list_str.replace(/
  874
+			(\n)?							// leading line = $1
  875
+			(^[ \t]*)						// leading whitespace = $2
  876
+			([*+-]|\d+[.]) [ \t]+			// list marker = $3
  877
+			([^\r]+?						// list item text   = $4
  878
+			(\n{1,2}))
  879
+			(?= \n* (~0 | \2 ([*+-]|\d+[.]) [ \t]+))
  880
+		/gm, function(){...});
  881
+	*/
  882
+	list_str = list_str.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm,
  883
+		function(wholeMatch,m1,m2,m3,m4){
  884
+			var item = m4;
  885
+			var leading_line = m1;
  886
+			var leading_space = m2;
  887
+
  888
+			if (leading_line || (item.search(/\n{2,}/)>-1)) {
  889
+				item = _RunBlockGamut(_Outdent(item));
  890
+			}
  891
+			else {
  892
+				// Recursion for sub-lists:
  893
+				item = _DoLists(_Outdent(item));
  894
+				item = item.replace(/\n$/,""); // chomp(item)
  895
+				item = _RunSpanGamut(item);
  896
+			}
  897
+
  898
+			return  "<li>" + item + "</li>\n";
  899
+		}
  900
+	);
  901
+
  902
+	// attacklab: strip sentinel
  903
+	list_str = list_str.replace(/~0/g,"");
  904
+
  905
+	g_list_level--;
  906
+	return list_str;
  907
+}
  908
+
  909
+
  910
+var _DoCodeBlocks = function(text) {
  911
+//
  912
+//  Process Markdown `<pre><code>` blocks.
  913
+//
  914
+
  915
+	/*
  916
+		text = text.replace(text,
  917
+			/(?:\n\n|^)
  918
+			(								// $1 = the code block -- one or more lines, starting with a space/tab
  919
+				(?:
  920
+					(?:[ ]{4}|\t)			// Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
  921
+					.*\n+
  922
+				)+
  923
+			)
  924
+			(\n*[ ]{0,3}[^ \t\n]|(?=~0))	// attacklab: g_tab_width
  925
+		/g,function(){...});
  926
+	*/
  927
+
  928
+	// attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
  929
+	text += "~0";
  930
+
  931
+	text = text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,
  932
+		function(wholeMatch,m1,m2) {
  933
+			var codeblock = m1;
  934
+			var nextChar = m2;
  935
+
  936
+			codeblock = _EncodeCode( _Outdent(codeblock));
  937
+			codeblock = _Detab(codeblock);
  938
+			codeblock = codeblock.replace(/^\n+/g,""); // trim leading newlines
  939
+			codeblock = codeblock.replace(/\n+$/g,""); // trim trailing whitespace
  940
+
  941
+			codeblock = "<pre><code>" + codeblock + "\n</code></pre>";
  942
+
  943
+			return hashBlock(codeblock) + nextChar;
  944
+		}
  945
+	);
  946
+
  947
+	// attacklab: strip sentinel
  948
+	text = text.replace(/~0/,"");
  949
+
  950
+	return text;
  951
+}
  952
+
  953
+var hashBlock = function(text) {
  954
+	text = text.replace(/(^\n+|\n+$)/g,"");
  955
+	return "\n\n~K" + (g_html_blocks.push(text)-1) + "K\n\n";
  956
+}
  957
+
  958
+
  959
+var _DoCodeSpans = function(text) {
  960
+//
  961
+//   *  Backtick quotes are used for <code></code> spans.
  962
+//
  963
+//   *  You can use multiple backticks as the delimiters if you want to
  964
+//	 include literal backticks in the code span. So, this input:
  965
+//
  966
+//		 Just type ``foo `bar` baz`` at the prompt.
  967
+//
  968
+//	   Will translate to:
  969
+//
  970
+//		 <p>Just type <code>foo `bar` baz</code> at the prompt.</p>
  971
+//
  972
+//	There's no arbitrary limit to the number of backticks you
  973
+//	can use as delimters. If you need three consecutive backticks
  974
+//	in your code, use four for delimiters, etc.
  975
+//
  976
+//  *  You can use spaces to get literal backticks at the edges:
  977
+//
  978
+//		 ... type `` `bar` `` ...
  979
+//
  980
+//	   Turns to:
  981
+//
  982
+//		 ... type <code>`bar`</code> ...
  983
+//
  984
+
  985
+	/*
  986
+		text = text.replace(/
  987
+			(^|[^\\])					// Character before opening ` can't be a backslash
  988
+			(`+)						// $2 = Opening run of `
  989
+			(							// $3 = The code block
  990
+				[^\r]*?
  991
+				[^`]					// attacklab: work around lack of lookbehind
  992
+			)
  993
+			\2							// Matching closer
  994
+			(?!`)
  995
+		/gm, function(){...});
  996
+	*/
  997
+
  998
+	text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,
  999
+		function(wholeMatch,m1,m2,m3,m4) {
  1000
+			var c = m3;
  1001
+			c = c.replace(/^([ \t]*)/g,"");	// leading whitespace
  1002
+			c = c.replace(/[ \t]*$/g,"");	// trailing whitespace
  1003
+			c = _EncodeCode(c);
  1004
+			return m1+"<code>"+c+"</code>";
  1005
+		});
  1006
+
  1007
+	return text;
  1008
+}
  1009
+
  1010
+
  1011
+var _EncodeCode = function(text) {
  1012
+//
  1013
+// Encode/escape certain characters inside Markdown code runs.
  1014
+// The point is that in code, these characters are literals,
  1015
+// and lose their special Markdown meanings.
  1016
+//
  1017
+	// Encode all ampersands; HTML entities are not
  1018
+	// entities within a Markdown code span.
  1019
+	text = text.replace(/&/g,"&amp;");
  1020
+
  1021
+	// Do the angle bracket song and dance:
  1022
+	text = text.replace(/</g,"&lt;");
  1023
+	text = text.replace(/>/g,"&gt;");
  1024
+
  1025
+	// Now, escape characters that are magic in Markdown:
  1026
+	text = escapeCharacters(text,"\*_{}[]\\",false);
  1027
+
  1028
+// jj the line above breaks this:
  1029
+//---
  1030
+
  1031
+//* Item
  1032
+
  1033
+//   1. Subitem
  1034
+
  1035
+//            special char: *
  1036
+//---
  1037
+
  1038
+	return text;
  1039
+}
  1040
+
  1041
+
  1042
+var _DoItalicsAndBold = function(text) {
  1043
+
  1044
+	// <strong> must go first:
  1045
+	text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g,
  1046
+		"<strong>$2</strong>");
  1047
+
  1048
+	text = text.replace(/(\w)_(\w)/g, "$1~E95E$2") // ** GFM **  "~E95E" == escaped "_"
  1049
+	text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g,
  1050
+		"<em>$2</em>");
  1051
+
  1052
+	return text;
  1053
+}
  1054
+
  1055
+
  1056
+var _DoBlockQuotes = function(text) {
  1057
+
  1058
+	/*
  1059
+		text = text.replace(/
  1060
+		(								// Wrap whole match in $1
  1061
+			(
  1062
+				^[ \t]*>[ \t]?			// '>' at the start of a line
  1063
+				.+\n					// rest of the first line
  1064
+				(.+\n)*					// subsequent consecutive lines
  1065
+				\n*						// blanks
  1066
+			)+
  1067
+		)
  1068
+		/gm, function(){...});
  1069
+	*/
  1070
+
  1071
+	text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,
  1072
+		function(wholeMatch,m1) {
  1073
+			var bq = m1;
  1074
+
  1075
+			// attacklab: hack around Konqueror 3.5.4 bug:
  1076
+			// "----------bug".replace(/^-/g,"") == "bug"
  1077
+
  1078
+			bq = bq.replace(/^[ \t]*>[ \t]?/gm,"~0");	// trim one level of quoting
  1079
+
  1080
+			// attacklab: clean up hack
  1081
+			bq = bq.replace(/~0/g,"");
  1082
+
  1083
+			bq = bq.replace(/^[ \t]+$/gm,"");		// trim whitespace-only lines
  1084
+			bq = _RunBlockGamut(bq);				// recurse
  1085
+
  1086
+			bq = bq.replace(/(^|\n)/g,"$1  ");
  1087
+			// These leading spaces screw with <pre> content, so we need to fix that:
  1088
+			bq = bq.replace(
  1089
+					/(\s*<pre>[^\r]+?<\/pre>)/gm,
  1090
+				function(wholeMatch,m1) {
  1091
+					var pre = m1;
  1092
+					// attacklab: hack around Konqueror 3.5.4 bug:
  1093
+					pre = pre.replace(/^  /mg,"~0");
  1094
+					pre = pre.replace(/~0/g,"");
  1095
+					return pre;
  1096
+				});
  1097
+
  1098
+			return hashBlock("<blockquote>\n" + bq + "\n</blockquote>");
  1099
+		});
  1100
+	return text;
  1101
+}
  1102
+
  1103
+
  1104
+var _FormParagraphs = function(text) {
  1105
+//
  1106
+//  Params:
  1107
+//    $text - string to process with html <p> tags
  1108
+//
  1109
+
  1110
+	// Strip leading and trailing lines:
  1111
+	text = text.replace(/^\n+/g,"");
  1112
+	text = text.replace(/\n+$/g,"");
  1113
+
  1114
+	var grafs = text.split(/\n{2,}/g);
  1115
+	var grafsOut = new Array();
  1116
+
  1117
+	//
  1118
+	// Wrap <p> tags.
  1119
+	//
  1120
+	var end = grafs.length;
  1121
+	for (var i=0; i<end; i++) {
  1122
+		var str = grafs[i];
  1123
+
  1124
+		// if this is an HTML marker, copy it
  1125
+		if (str.search(/~K(\d+)K/g) >= 0) {
  1126
+			grafsOut.push(str);
  1127
+		}
  1128
+		else if (str.search(/\S/) >= 0) {
  1129
+			str = _RunSpanGamut(str);
  1130
+			str = str.replace(/\n/g,"<br />");  // ** GFM **
  1131
+			str = str.replace(/^([ \t]*)/g,"<p>");
  1132
+			str += "</p>"
  1133
+			grafsOut.push(str);
  1134
+		}
  1135
+
  1136
+	}
  1137
+
  1138
+	//
  1139
+	// Unhashify HTML blocks
  1140
+	//
  1141
+	end = grafsOut.length;
  1142
+	for (var i=0; i<end; i++) {
  1143
+		// if this is a marker for an html block...
  1144
+		while (grafsOut[i].search(/~K(\d+)K/) >= 0) {
  1145
+			var blockText = g_html_blocks[RegExp.$1];
  1146
+			blockText = blockText.replace(/\$/g,"$$$$"); // Escape any dollar signs
  1147
+			grafsOut[i] = grafsOut[i].replace(/~K\d+K/,blockText);
  1148
+		}
  1149
+	}
  1150
+
  1151
+	return grafsOut.join("\n\n");
  1152
+}
  1153
+
  1154
+
  1155
+var _EncodeAmpsAndAngles = function(text) {
  1156
+// Smart processing for ampersands and angle brackets that need to be encoded.
  1157
+
  1158
+	// Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
  1159
+	//   http://bumppo.net/projects/amputator/
  1160
+	text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&amp;");
  1161
+
  1162
+	// Encode naked <'s
  1163
+	text = text.replace(/<(?![a-z\/?\$!])/gi,"&lt;");
  1164
+
  1165
+	return text;
  1166
+}
  1167
+
  1168
+
  1169
+var _EncodeBackslashEscapes = function(text) {
  1170
+//
  1171
+//   Parameter:  String.
  1172
+//   Returns:	The string, with after processing the following backslash
  1173
+//			   escape sequences.
  1174
+//
  1175
+
  1176
+	// attacklab: The polite way to do this is with the new
  1177
+	// escapeCharacters() function:
  1178
+	//
  1179
+	// 	text = escapeCharacters(text,"\\",true);
  1180
+	// 	text = escapeCharacters(text,"`*_{}[]()>#+-.!",true);
  1181
+	//
  1182
+	// ...but we're sidestepping its use of the (slow) RegExp constructor
  1183
+	// as an optimization for Firefox.  This function gets called a LOT.
  1184
+
  1185
+	text = text.replace(/\\(\\)/g,escapeCharacters_callback);
  1186
+	text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g,escapeCharacters_callback);
  1187
+	return text;
  1188
+}
</