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