github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/themes/wind/static/libs/toopay-bootstrap-markdown/js/markdown.js (about) 1 // Released under MIT license 2 // Copyright (c) 2009-2010 Dominic Baggott 3 // Copyright (c) 2009-2010 Ash Berlin 4 // Copyright (c) 2011 Christoph Dorn <christoph@christophdorn.com> (http://www.christophdorn.com) 5 6 (function( expose ) { 7 8 /** 9 * class Markdown 10 * 11 * Markdown processing in Javascript done right. We have very particular views 12 * on what constitutes 'right' which include: 13 * 14 * - produces well-formed HTML (this means that em and strong nesting is 15 * important) 16 * 17 * - has an intermediate representation to allow processing of parsed data (We 18 * in fact have two, both as [JsonML]: a markdown tree and an HTML tree). 19 * 20 * - is easily extensible to add new dialects without having to rewrite the 21 * entire parsing mechanics 22 * 23 * - has a good test suite 24 * 25 * This implementation fulfills all of these (except that the test suite could 26 * do with expanding to automatically run all the fixtures from other Markdown 27 * implementations.) 28 * 29 * ##### Intermediate Representation 30 * 31 * *TODO* Talk about this :) Its JsonML, but document the node names we use. 32 * 33 * [JsonML]: http://jsonml.org/ "JSON Markup Language" 34 **/ 35 var Markdown = expose.Markdown = function Markdown(dialect) { 36 switch (typeof dialect) { 37 case "undefined": 38 this.dialect = Markdown.dialects.Gruber; 39 break; 40 case "object": 41 this.dialect = dialect; 42 break; 43 default: 44 if (dialect in Markdown.dialects) { 45 this.dialect = Markdown.dialects[dialect]; 46 } 47 else { 48 throw new Error("Unknown Markdown dialect '" + String(dialect) + "'"); 49 } 50 break; 51 } 52 this.em_state = []; 53 this.strong_state = []; 54 this.debug_indent = ""; 55 }; 56 57 /** 58 * parse( markdown, [dialect] ) -> JsonML 59 * - markdown (String): markdown string to parse 60 * - dialect (String | Dialect): the dialect to use, defaults to gruber 61 * 62 * Parse `markdown` and return a markdown document as a Markdown.JsonML tree. 63 **/ 64 expose.parse = function( source, dialect ) { 65 // dialect will default if undefined 66 var md = new Markdown( dialect ); 67 return md.toTree( source ); 68 }; 69 70 /** 71 * toHTML( markdown, [dialect] ) -> String 72 * toHTML( md_tree ) -> String 73 * - markdown (String): markdown string to parse 74 * - md_tree (Markdown.JsonML): parsed markdown tree 75 * 76 * Take markdown (either as a string or as a JsonML tree) and run it through 77 * [[toHTMLTree]] then turn it into a well-formated HTML fragment. 78 **/ 79 expose.toHTML = function toHTML( source , dialect , options ) { 80 var input = expose.toHTMLTree( source , dialect , options ); 81 82 return expose.renderJsonML( input ); 83 }; 84 85 /** 86 * toHTMLTree( markdown, [dialect] ) -> JsonML 87 * toHTMLTree( md_tree ) -> JsonML 88 * - markdown (String): markdown string to parse 89 * - dialect (String | Dialect): the dialect to use, defaults to gruber 90 * - md_tree (Markdown.JsonML): parsed markdown tree 91 * 92 * Turn markdown into HTML, represented as a JsonML tree. If a string is given 93 * to this function, it is first parsed into a markdown tree by calling 94 * [[parse]]. 95 **/ 96 expose.toHTMLTree = function toHTMLTree( input, dialect , options ) { 97 // convert string input to an MD tree 98 if ( typeof input ==="string" ) input = this.parse( input, dialect ); 99 100 // Now convert the MD tree to an HTML tree 101 102 // remove references from the tree 103 var attrs = extract_attr( input ), 104 refs = {}; 105 106 if ( attrs && attrs.references ) { 107 refs = attrs.references; 108 } 109 110 var html = convert_tree_to_html( input, refs , options ); 111 merge_text_nodes( html ); 112 return html; 113 }; 114 115 // For Spidermonkey based engines 116 function mk_block_toSource() { 117 return "Markdown.mk_block( " + 118 uneval(this.toString()) + 119 ", " + 120 uneval(this.trailing) + 121 ", " + 122 uneval(this.lineNumber) + 123 " )"; 124 } 125 126 // node 127 function mk_block_inspect() { 128 var util = require('util'); 129 return "Markdown.mk_block( " + 130 util.inspect(this.toString()) + 131 ", " + 132 util.inspect(this.trailing) + 133 ", " + 134 util.inspect(this.lineNumber) + 135 " )"; 136 137 } 138 139 var mk_block = Markdown.mk_block = function(block, trail, line) { 140 // Be helpful for default case in tests. 141 if ( arguments.length == 1 ) trail = "\n\n"; 142 143 var s = new String(block); 144 s.trailing = trail; 145 // To make it clear its not just a string 146 s.inspect = mk_block_inspect; 147 s.toSource = mk_block_toSource; 148 149 if (line != undefined) 150 s.lineNumber = line; 151 152 return s; 153 }; 154 155 function count_lines( str ) { 156 var n = 0, i = -1; 157 while ( ( i = str.indexOf('\n', i+1) ) !== -1) n++; 158 return n; 159 } 160 161 // Internal - split source into rough blocks 162 Markdown.prototype.split_blocks = function splitBlocks( input, startLine ) { 163 // [\s\S] matches _anything_ (newline or space) 164 var re = /([\s\S]+?)($|\n(?:\s*\n|$)+)/g, 165 blocks = [], 166 m; 167 168 var line_no = 1; 169 170 if ( ( m = /^(\s*\n)/.exec(input) ) != null ) { 171 // skip (but count) leading blank lines 172 line_no += count_lines( m[0] ); 173 re.lastIndex = m[0].length; 174 } 175 176 while ( ( m = re.exec(input) ) !== null ) { 177 blocks.push( mk_block( m[1], m[2], line_no ) ); 178 line_no += count_lines( m[0] ); 179 } 180 181 return blocks; 182 }; 183 184 /** 185 * Markdown#processBlock( block, next ) -> undefined | [ JsonML, ... ] 186 * - block (String): the block to process 187 * - next (Array): the following blocks 188 * 189 * Process `block` and return an array of JsonML nodes representing `block`. 190 * 191 * It does this by asking each block level function in the dialect to process 192 * the block until one can. Succesful handling is indicated by returning an 193 * array (with zero or more JsonML nodes), failure by a false value. 194 * 195 * Blocks handlers are responsible for calling [[Markdown#processInline]] 196 * themselves as appropriate. 197 * 198 * If the blocks were split incorrectly or adjacent blocks need collapsing you 199 * can adjust `next` in place using shift/splice etc. 200 * 201 * If any of this default behaviour is not right for the dialect, you can 202 * define a `__call__` method on the dialect that will get invoked to handle 203 * the block processing. 204 */ 205 Markdown.prototype.processBlock = function processBlock( block, next ) { 206 var cbs = this.dialect.block, 207 ord = cbs.__order__; 208 209 if ( "__call__" in cbs ) { 210 return cbs.__call__.call(this, block, next); 211 } 212 213 for ( var i = 0; i < ord.length; i++ ) { 214 //D:this.debug( "Testing", ord[i] ); 215 var res = cbs[ ord[i] ].call( this, block, next ); 216 if ( res ) { 217 //D:this.debug(" matched"); 218 if ( !isArray(res) || ( res.length > 0 && !( isArray(res[0]) ) ) ) 219 this.debug(ord[i], "didn't return a proper array"); 220 //D:this.debug( "" ); 221 return res; 222 } 223 } 224 225 // Uhoh! no match! Should we throw an error? 226 return []; 227 }; 228 229 Markdown.prototype.processInline = function processInline( block ) { 230 return this.dialect.inline.__call__.call( this, String( block ) ); 231 }; 232 233 /** 234 * Markdown#toTree( source ) -> JsonML 235 * - source (String): markdown source to parse 236 * 237 * Parse `source` into a JsonML tree representing the markdown document. 238 **/ 239 // custom_tree means set this.tree to `custom_tree` and restore old value on return 240 Markdown.prototype.toTree = function toTree( source, custom_root ) { 241 var blocks = source instanceof Array ? source : this.split_blocks( source ); 242 243 // Make tree a member variable so its easier to mess with in extensions 244 var old_tree = this.tree; 245 try { 246 this.tree = custom_root || this.tree || [ "markdown" ]; 247 248 blocks: 249 while ( blocks.length ) { 250 var b = this.processBlock( blocks.shift(), blocks ); 251 252 // Reference blocks and the like won't return any content 253 if ( !b.length ) continue blocks; 254 255 this.tree.push.apply( this.tree, b ); 256 } 257 return this.tree; 258 } 259 finally { 260 if ( custom_root ) { 261 this.tree = old_tree; 262 } 263 } 264 }; 265 266 // Noop by default 267 Markdown.prototype.debug = function () { 268 var args = Array.prototype.slice.call( arguments); 269 args.unshift(this.debug_indent); 270 if (typeof print !== "undefined") 271 print.apply( print, args ); 272 if (typeof console !== "undefined" && typeof console.log !== "undefined") 273 console.log.apply( null, args ); 274 } 275 276 Markdown.prototype.loop_re_over_block = function( re, block, cb ) { 277 // Dont use /g regexps with this 278 var m, 279 b = block.valueOf(); 280 281 while ( b.length && (m = re.exec(b) ) != null) { 282 b = b.substr( m[0].length ); 283 cb.call(this, m); 284 } 285 return b; 286 }; 287 288 /** 289 * Markdown.dialects 290 * 291 * Namespace of built-in dialects. 292 **/ 293 Markdown.dialects = {}; 294 295 /** 296 * Markdown.dialects.Gruber 297 * 298 * The default dialect that follows the rules set out by John Gruber's 299 * markdown.pl as closely as possible. Well actually we follow the behaviour of 300 * that script which in some places is not exactly what the syntax web page 301 * says. 302 **/ 303 Markdown.dialects.Gruber = { 304 block: { 305 atxHeader: function atxHeader( block, next ) { 306 var m = block.match( /^(#{1,6})\s*(.*?)\s*#*\s*(?:\n|$)/ ); 307 308 if ( !m ) return undefined; 309 310 var header = [ "header", { level: m[ 1 ].length } ]; 311 Array.prototype.push.apply(header, this.processInline(m[ 2 ])); 312 313 if ( m[0].length < block.length ) 314 next.unshift( mk_block( block.substr( m[0].length ), block.trailing, block.lineNumber + 2 ) ); 315 316 return [ header ]; 317 }, 318 319 setextHeader: function setextHeader( block, next ) { 320 var m = block.match( /^(.*)\n([-=])\2\2+(?:\n|$)/ ); 321 322 if ( !m ) return undefined; 323 324 var level = ( m[ 2 ] === "=" ) ? 1 : 2; 325 var header = [ "header", { level : level }, m[ 1 ] ]; 326 327 if ( m[0].length < block.length ) 328 next.unshift( mk_block( block.substr( m[0].length ), block.trailing, block.lineNumber + 2 ) ); 329 330 return [ header ]; 331 }, 332 333 code: function code( block, next ) { 334 // | Foo 335 // |bar 336 // should be a code block followed by a paragraph. Fun 337 // 338 // There might also be adjacent code block to merge. 339 340 var ret = [], 341 re = /^(?: {0,3}\t| {4})(.*)\n?/, 342 lines; 343 344 // 4 spaces + content 345 if ( !block.match( re ) ) return undefined; 346 347 block_search: 348 do { 349 // Now pull out the rest of the lines 350 var b = this.loop_re_over_block( 351 re, block.valueOf(), function( m ) { ret.push( m[1] ); } ); 352 353 if (b.length) { 354 // Case alluded to in first comment. push it back on as a new block 355 next.unshift( mk_block(b, block.trailing) ); 356 break block_search; 357 } 358 else if (next.length) { 359 // Check the next block - it might be code too 360 if ( !next[0].match( re ) ) break block_search; 361 362 // Pull how how many blanks lines follow - minus two to account for .join 363 ret.push ( block.trailing.replace(/[^\n]/g, '').substring(2) ); 364 365 block = next.shift(); 366 } 367 else { 368 break block_search; 369 } 370 } while (true); 371 372 return [ [ "code_block", ret.join("\n") ] ]; 373 }, 374 375 horizRule: function horizRule( block, next ) { 376 // this needs to find any hr in the block to handle abutting blocks 377 var m = block.match( /^(?:([\s\S]*?)\n)?[ \t]*([-_*])(?:[ \t]*\2){2,}[ \t]*(?:\n([\s\S]*))?$/ ); 378 379 if ( !m ) { 380 return undefined; 381 } 382 383 var jsonml = [ [ "hr" ] ]; 384 385 // if there's a leading abutting block, process it 386 if ( m[ 1 ] ) { 387 jsonml.unshift.apply( jsonml, this.processBlock( m[ 1 ], [] ) ); 388 } 389 390 // if there's a trailing abutting block, stick it into next 391 if ( m[ 3 ] ) { 392 next.unshift( mk_block( m[ 3 ] ) ); 393 } 394 395 return jsonml; 396 }, 397 398 // There are two types of lists. Tight and loose. Tight lists have no whitespace 399 // between the items (and result in text just in the <li>) and loose lists, 400 // which have an empty line between list items, resulting in (one or more) 401 // paragraphs inside the <li>. 402 // 403 // There are all sorts weird edge cases about the original markdown.pl's 404 // handling of lists: 405 // 406 // * Nested lists are supposed to be indented by four chars per level. But 407 // if they aren't, you can get a nested list by indenting by less than 408 // four so long as the indent doesn't match an indent of an existing list 409 // item in the 'nest stack'. 410 // 411 // * The type of the list (bullet or number) is controlled just by the 412 // first item at the indent. Subsequent changes are ignored unless they 413 // are for nested lists 414 // 415 lists: (function( ) { 416 // Use a closure to hide a few variables. 417 var any_list = "[*+-]|\\d+\\.", 418 bullet_list = /[*+-]/, 419 number_list = /\d+\./, 420 // Capture leading indent as it matters for determining nested lists. 421 is_list_re = new RegExp( "^( {0,3})(" + any_list + ")[ \t]+" ), 422 indent_re = "(?: {0,3}\\t| {4})"; 423 424 // TODO: Cache this regexp for certain depths. 425 // Create a regexp suitable for matching an li for a given stack depth 426 function regex_for_depth( depth ) { 427 428 return new RegExp( 429 // m[1] = indent, m[2] = list_type 430 "(?:^(" + indent_re + "{0," + depth + "} {0,3})(" + any_list + ")\\s+)|" + 431 // m[3] = cont 432 "(^" + indent_re + "{0," + (depth-1) + "}[ ]{0,4})" 433 ); 434 } 435 function expand_tab( input ) { 436 return input.replace( / {0,3}\t/g, " " ); 437 } 438 439 // Add inline content `inline` to `li`. inline comes from processInline 440 // so is an array of content 441 function add(li, loose, inline, nl) { 442 if (loose) { 443 li.push( [ "para" ].concat(inline) ); 444 return; 445 } 446 // Hmmm, should this be any block level element or just paras? 447 var add_to = li[li.length -1] instanceof Array && li[li.length - 1][0] == "para" 448 ? li[li.length -1] 449 : li; 450 451 // If there is already some content in this list, add the new line in 452 if (nl && li.length > 1) inline.unshift(nl); 453 454 for (var i=0; i < inline.length; i++) { 455 var what = inline[i], 456 is_str = typeof what == "string"; 457 if (is_str && add_to.length > 1 && typeof add_to[add_to.length-1] == "string" ) { 458 add_to[ add_to.length-1 ] += what; 459 } 460 else { 461 add_to.push( what ); 462 } 463 } 464 } 465 466 // contained means have an indent greater than the current one. On 467 // *every* line in the block 468 function get_contained_blocks( depth, blocks ) { 469 470 var re = new RegExp( "^(" + indent_re + "{" + depth + "}.*?\\n?)*$" ), 471 replace = new RegExp("^" + indent_re + "{" + depth + "}", "gm"), 472 ret = []; 473 474 while ( blocks.length > 0 ) { 475 if ( re.exec( blocks[0] ) ) { 476 var b = blocks.shift(), 477 // Now remove that indent 478 x = b.replace( replace, ""); 479 480 ret.push( mk_block( x, b.trailing, b.lineNumber ) ); 481 } 482 break; 483 } 484 return ret; 485 } 486 487 // passed to stack.forEach to turn list items up the stack into paras 488 function paragraphify(s, i, stack) { 489 var list = s.list; 490 var last_li = list[list.length-1]; 491 492 if (last_li[1] instanceof Array && last_li[1][0] == "para") { 493 return; 494 } 495 if (i+1 == stack.length) { 496 // Last stack frame 497 // Keep the same array, but replace the contents 498 last_li.push( ["para"].concat( last_li.splice(1) ) ); 499 } 500 else { 501 var sublist = last_li.pop(); 502 last_li.push( ["para"].concat( last_li.splice(1) ), sublist ); 503 } 504 } 505 506 // The matcher function 507 return function( block, next ) { 508 var m = block.match( is_list_re ); 509 if ( !m ) return undefined; 510 511 function make_list( m ) { 512 var list = bullet_list.exec( m[2] ) 513 ? ["bulletlist"] 514 : ["numberlist"]; 515 516 stack.push( { list: list, indent: m[1] } ); 517 return list; 518 } 519 520 521 var stack = [], // Stack of lists for nesting. 522 list = make_list( m ), 523 last_li, 524 loose = false, 525 ret = [ stack[0].list ], 526 i; 527 528 // Loop to search over block looking for inner block elements and loose lists 529 loose_search: 530 while( true ) { 531 // Split into lines preserving new lines at end of line 532 var lines = block.split( /(?=\n)/ ); 533 534 // We have to grab all lines for a li and call processInline on them 535 // once as there are some inline things that can span lines. 536 var li_accumulate = ""; 537 538 // Loop over the lines in this block looking for tight lists. 539 tight_search: 540 for (var line_no=0; line_no < lines.length; line_no++) { 541 var nl = "", 542 l = lines[line_no].replace(/^\n/, function(n) { nl = n; return ""; }); 543 544 // TODO: really should cache this 545 var line_re = regex_for_depth( stack.length ); 546 547 m = l.match( line_re ); 548 //print( "line:", uneval(l), "\nline match:", uneval(m) ); 549 550 // We have a list item 551 if ( m[1] !== undefined ) { 552 // Process the previous list item, if any 553 if ( li_accumulate.length ) { 554 add( last_li, loose, this.processInline( li_accumulate ), nl ); 555 // Loose mode will have been dealt with. Reset it 556 loose = false; 557 li_accumulate = ""; 558 } 559 560 m[1] = expand_tab( m[1] ); 561 var wanted_depth = Math.floor(m[1].length/4)+1; 562 //print( "want:", wanted_depth, "stack:", stack.length); 563 if ( wanted_depth > stack.length ) { 564 // Deep enough for a nested list outright 565 //print ( "new nested list" ); 566 list = make_list( m ); 567 last_li.push( list ); 568 last_li = list[1] = [ "listitem" ]; 569 } 570 else { 571 // We aren't deep enough to be strictly a new level. This is 572 // where Md.pl goes nuts. If the indent matches a level in the 573 // stack, put it there, else put it one deeper then the 574 // wanted_depth deserves. 575 var found = false; 576 for (i = 0; i < stack.length; i++) { 577 if ( stack[ i ].indent != m[1] ) continue; 578 list = stack[ i ].list; 579 stack.splice( i+1 ); 580 found = true; 581 break; 582 } 583 584 if (!found) { 585 //print("not found. l:", uneval(l)); 586 wanted_depth++; 587 if (wanted_depth <= stack.length) { 588 stack.splice(wanted_depth); 589 //print("Desired depth now", wanted_depth, "stack:", stack.length); 590 list = stack[wanted_depth-1].list; 591 //print("list:", uneval(list) ); 592 } 593 else { 594 //print ("made new stack for messy indent"); 595 list = make_list(m); 596 last_li.push(list); 597 } 598 } 599 600 //print( uneval(list), "last", list === stack[stack.length-1].list ); 601 last_li = [ "listitem" ]; 602 list.push(last_li); 603 } // end depth of shenegains 604 nl = ""; 605 } 606 607 // Add content 608 if (l.length > m[0].length) { 609 li_accumulate += nl + l.substr( m[0].length ); 610 } 611 } // tight_search 612 613 if ( li_accumulate.length ) { 614 add( last_li, loose, this.processInline( li_accumulate ), nl ); 615 // Loose mode will have been dealt with. Reset it 616 loose = false; 617 li_accumulate = ""; 618 } 619 620 // Look at the next block - we might have a loose list. Or an extra 621 // paragraph for the current li 622 var contained = get_contained_blocks( stack.length, next ); 623 624 // Deal with code blocks or properly nested lists 625 if (contained.length > 0) { 626 // Make sure all listitems up the stack are paragraphs 627 forEach( stack, paragraphify, this); 628 629 last_li.push.apply( last_li, this.toTree( contained, [] ) ); 630 } 631 632 var next_block = next[0] && next[0].valueOf() || ""; 633 634 if ( next_block.match(is_list_re) || next_block.match( /^ / ) ) { 635 block = next.shift(); 636 637 // Check for an HR following a list: features/lists/hr_abutting 638 var hr = this.dialect.block.horizRule( block, next ); 639 640 if (hr) { 641 ret.push.apply(ret, hr); 642 break; 643 } 644 645 // Make sure all listitems up the stack are paragraphs 646 forEach( stack, paragraphify, this); 647 648 loose = true; 649 continue loose_search; 650 } 651 break; 652 } // loose_search 653 654 return ret; 655 }; 656 })(), 657 658 blockquote: function blockquote( block, next ) { 659 if ( !block.match( /^>/m ) ) 660 return undefined; 661 662 var jsonml = []; 663 664 // separate out the leading abutting block, if any 665 if ( block[ 0 ] != ">" ) { 666 var lines = block.split( /\n/ ), 667 prev = []; 668 669 // keep shifting lines until you find a crotchet 670 while ( lines.length && lines[ 0 ][ 0 ] != ">" ) { 671 prev.push( lines.shift() ); 672 } 673 674 // reassemble! 675 block = lines.join( "\n" ); 676 jsonml.push.apply( jsonml, this.processBlock( prev.join( "\n" ), [] ) ); 677 } 678 679 // if the next block is also a blockquote merge it in 680 while ( next.length && next[ 0 ][ 0 ] == ">" ) { 681 var b = next.shift(); 682 block = new String(block + block.trailing + b); 683 block.trailing = b.trailing; 684 } 685 686 // Strip off the leading "> " and re-process as a block. 687 var input = block.replace( /^> ?/gm, '' ), 688 old_tree = this.tree; 689 jsonml.push( this.toTree( input, [ "blockquote" ] ) ); 690 691 return jsonml; 692 }, 693 694 referenceDefn: function referenceDefn( block, next) { 695 var re = /^\s*\[(.*?)\]:\s*(\S+)(?:\s+(?:(['"])(.*?)\3|\((.*?)\)))?\n?/; 696 // interesting matches are [ , ref_id, url, , title, title ] 697 698 if ( !block.match(re) ) 699 return undefined; 700 701 // make an attribute node if it doesn't exist 702 if ( !extract_attr( this.tree ) ) { 703 this.tree.splice( 1, 0, {} ); 704 } 705 706 var attrs = extract_attr( this.tree ); 707 708 // make a references hash if it doesn't exist 709 if ( attrs.references === undefined ) { 710 attrs.references = {}; 711 } 712 713 var b = this.loop_re_over_block(re, block, function( m ) { 714 715 if ( m[2] && m[2][0] == '<' && m[2][m[2].length-1] == '>' ) 716 m[2] = m[2].substring( 1, m[2].length - 1 ); 717 718 var ref = attrs.references[ m[1].toLowerCase() ] = { 719 href: m[2] 720 }; 721 722 if (m[4] !== undefined) 723 ref.title = m[4]; 724 else if (m[5] !== undefined) 725 ref.title = m[5]; 726 727 } ); 728 729 if (b.length) 730 next.unshift( mk_block( b, block.trailing ) ); 731 732 return []; 733 }, 734 735 para: function para( block, next ) { 736 // everything's a para! 737 return [ ["para"].concat( this.processInline( block ) ) ]; 738 } 739 } 740 }; 741 742 Markdown.dialects.Gruber.inline = { 743 744 __oneElement__: function oneElement( text, patterns_or_re, previous_nodes ) { 745 var m, 746 res, 747 lastIndex = 0; 748 749 patterns_or_re = patterns_or_re || this.dialect.inline.__patterns__; 750 var re = new RegExp( "([\\s\\S]*?)(" + (patterns_or_re.source || patterns_or_re) + ")" ); 751 752 m = re.exec( text ); 753 if (!m) { 754 // Just boring text 755 return [ text.length, text ]; 756 } 757 else if ( m[1] ) { 758 // Some un-interesting text matched. Return that first 759 return [ m[1].length, m[1] ]; 760 } 761 762 var res; 763 if ( m[2] in this.dialect.inline ) { 764 res = this.dialect.inline[ m[2] ].call( 765 this, 766 text.substr( m.index ), m, previous_nodes || [] ); 767 } 768 // Default for now to make dev easier. just slurp special and output it. 769 res = res || [ m[2].length, m[2] ]; 770 return res; 771 }, 772 773 __call__: function inline( text, patterns ) { 774 775 var out = [], 776 res; 777 778 function add(x) { 779 //D:self.debug(" adding output", uneval(x)); 780 if (typeof x == "string" && typeof out[out.length-1] == "string") 781 out[ out.length-1 ] += x; 782 else 783 out.push(x); 784 } 785 786 while ( text.length > 0 ) { 787 res = this.dialect.inline.__oneElement__.call(this, text, patterns, out ); 788 text = text.substr( res.shift() ); 789 forEach(res, add ) 790 } 791 792 return out; 793 }, 794 795 // These characters are intersting elsewhere, so have rules for them so that 796 // chunks of plain text blocks don't include them 797 "]": function () {}, 798 "}": function () {}, 799 800 "\\": function escaped( text ) { 801 // [ length of input processed, node/children to add... ] 802 // Only esacape: \ ` * _ { } [ ] ( ) # * + - . ! 803 if ( text.match( /^\\[\\`\*_{}\[\]()#\+.!\-]/ ) ) 804 return [ 2, text[1] ]; 805 else 806 // Not an esacpe 807 return [ 1, "\\" ]; 808 }, 809 810 " 816 // 1 2 3 4 <--- captures 817 var m = text.match( /^!\[(.*?)\][ \t]*\([ \t]*(\S*)(?:[ \t]+(["'])(.*?)\3)?[ \t]*\)/ ); 818 819 if ( m ) { 820 if ( m[2] && m[2][0] == '<' && m[2][m[2].length-1] == '>' ) 821 m[2] = m[2].substring( 1, m[2].length - 1 ); 822 823 m[2] = this.dialect.inline.__call__.call( this, m[2], /\\/ )[0]; 824 825 var attrs = { alt: m[1], href: m[2] || "" }; 826 if ( m[4] !== undefined) 827 attrs.title = m[4]; 828 829 return [ m[0].length, [ "img", attrs ] ]; 830 } 831 832 // ![Alt text][id] 833 m = text.match( /^!\[(.*?)\][ \t]*\[(.*?)\]/ ); 834 835 if ( m ) { 836 // We can't check if the reference is known here as it likely wont be 837 // found till after. Check it in md tree->hmtl tree conversion 838 return [ m[0].length, [ "img_ref", { alt: m[1], ref: m[2].toLowerCase(), original: m[0] } ] ]; 839 } 840 841 // Just consume the '![' 842 return [ 2, "![" ]; 843 }, 844 845 "[": function link( text ) { 846 847 var orig = String(text); 848 // Inline content is possible inside `link text` 849 var res = Markdown.DialectHelpers.inline_until_char.call( this, text.substr(1), ']' ); 850 851 // No closing ']' found. Just consume the [ 852 if ( !res ) return [ 1, '[' ]; 853 854 var consumed = 1 + res[ 0 ], 855 children = res[ 1 ], 856 link, 857 attrs; 858 859 // At this point the first [...] has been parsed. See what follows to find 860 // out which kind of link we are (reference or direct url) 861 text = text.substr( consumed ); 862 863 // [link text](/path/to/img.jpg "Optional title") 864 // 1 2 3 <--- captures 865 // This will capture up to the last paren in the block. We then pull 866 // back based on if there a matching ones in the url 867 // ([here](/url/(test)) 868 // The parens have to be balanced 869 var m = text.match( /^\s*\([ \t]*(\S+)(?:[ \t]+(["'])(.*?)\2)?[ \t]*\)/ ); 870 if ( m ) { 871 var url = m[1]; 872 consumed += m[0].length; 873 874 if ( url && url[0] == '<' && url[url.length-1] == '>' ) 875 url = url.substring( 1, url.length - 1 ); 876 877 // If there is a title we don't have to worry about parens in the url 878 if ( !m[3] ) { 879 var open_parens = 1; // One open that isn't in the capture 880 for (var len = 0; len < url.length; len++) { 881 switch ( url[len] ) { 882 case '(': 883 open_parens++; 884 break; 885 case ')': 886 if ( --open_parens == 0) { 887 consumed -= url.length - len; 888 url = url.substring(0, len); 889 } 890 break; 891 } 892 } 893 } 894 895 // Process escapes only 896 url = this.dialect.inline.__call__.call( this, url, /\\/ )[0]; 897 898 attrs = { href: url || "" }; 899 if ( m[3] !== undefined) 900 attrs.title = m[3]; 901 902 link = [ "link", attrs ].concat( children ); 903 return [ consumed, link ]; 904 } 905 906 // [Alt text][id] 907 // [Alt text] [id] 908 m = text.match( /^\s*\[(.*?)\]/ ); 909 910 if ( m ) { 911 912 consumed += m[ 0 ].length; 913 914 // [links][] uses links as its reference 915 attrs = { ref: ( m[ 1 ] || String(children) ).toLowerCase(), original: orig.substr( 0, consumed ) }; 916 917 link = [ "link_ref", attrs ].concat( children ); 918 919 // We can't check if the reference is known here as it likely wont be 920 // found till after. Check it in md tree->hmtl tree conversion. 921 // Store the original so that conversion can revert if the ref isn't found. 922 return [ consumed, link ]; 923 } 924 925 // [id] 926 // Only if id is plain (no formatting.) 927 if ( children.length == 1 && typeof children[0] == "string" ) { 928 929 attrs = { ref: children[0].toLowerCase(), original: orig.substr( 0, consumed ) }; 930 link = [ "link_ref", attrs, children[0] ]; 931 return [ consumed, link ]; 932 } 933 934 // Just consume the '[' 935 return [ 1, "[" ]; 936 }, 937 938 939 "<": function autoLink( text ) { 940 var m; 941 942 if ( ( m = text.match( /^<(?:((https?|ftp|mailto):[^>]+)|(.*?@.*?\.[a-zA-Z]+))>/ ) ) != null ) { 943 if ( m[3] ) { 944 return [ m[0].length, [ "link", { href: "mailto:" + m[3] }, m[3] ] ]; 945 946 } 947 else if ( m[2] == "mailto" ) { 948 return [ m[0].length, [ "link", { href: m[1] }, m[1].substr("mailto:".length ) ] ]; 949 } 950 else 951 return [ m[0].length, [ "link", { href: m[1] }, m[1] ] ]; 952 } 953 954 return [ 1, "<" ]; 955 }, 956 957 "`": function inlineCode( text ) { 958 // Inline code block. as many backticks as you like to start it 959 // Always skip over the opening ticks. 960 var m = text.match( /(`+)(([\s\S]*?)\1)/ ); 961 962 if ( m && m[2] ) 963 return [ m[1].length + m[2].length, [ "inlinecode", m[3] ] ]; 964 else { 965 // TODO: No matching end code found - warn! 966 return [ 1, "`" ]; 967 } 968 }, 969 970 " \n": function lineBreak( text ) { 971 return [ 3, [ "linebreak" ] ]; 972 } 973 974 }; 975 976 // Meta Helper/generator method for em and strong handling 977 function strong_em( tag, md ) { 978 979 var state_slot = tag + "_state", 980 other_slot = tag == "strong" ? "em_state" : "strong_state"; 981 982 function CloseTag(len) { 983 this.len_after = len; 984 this.name = "close_" + md; 985 } 986 987 return function ( text, orig_match ) { 988 989 if (this[state_slot][0] == md) { 990 // Most recent em is of this type 991 //D:this.debug("closing", md); 992 this[state_slot].shift(); 993 994 // "Consume" everything to go back to the recrusion in the else-block below 995 return[ text.length, new CloseTag(text.length-md.length) ]; 996 } 997 else { 998 // Store a clone of the em/strong states 999 var other = this[other_slot].slice(), 1000 state = this[state_slot].slice(); 1001 1002 this[state_slot].unshift(md); 1003 1004 //D:this.debug_indent += " "; 1005 1006 // Recurse 1007 var res = this.processInline( text.substr( md.length ) ); 1008 //D:this.debug_indent = this.debug_indent.substr(2); 1009 1010 var last = res[res.length - 1]; 1011 1012 //D:this.debug("processInline from", tag + ": ", uneval( res ) ); 1013 1014 var check = this[state_slot].shift(); 1015 if (last instanceof CloseTag) { 1016 res.pop(); 1017 // We matched! Huzzah. 1018 var consumed = text.length - last.len_after; 1019 return [ consumed, [ tag ].concat(res) ]; 1020 } 1021 else { 1022 // Restore the state of the other kind. We might have mistakenly closed it. 1023 this[other_slot] = other; 1024 this[state_slot] = state; 1025 1026 // We can't reuse the processed result as it could have wrong parsing contexts in it. 1027 return [ md.length, md ]; 1028 } 1029 } 1030 }; // End returned function 1031 } 1032 1033 Markdown.dialects.Gruber.inline["**"] = strong_em("strong", "**"); 1034 Markdown.dialects.Gruber.inline["__"] = strong_em("strong", "__"); 1035 Markdown.dialects.Gruber.inline["*"] = strong_em("em", "*"); 1036 Markdown.dialects.Gruber.inline["_"] = strong_em("em", "_"); 1037 1038 1039 // Build default order from insertion order. 1040 Markdown.buildBlockOrder = function(d) { 1041 var ord = []; 1042 for ( var i in d ) { 1043 if ( i == "__order__" || i == "__call__" ) continue; 1044 ord.push( i ); 1045 } 1046 d.__order__ = ord; 1047 }; 1048 1049 // Build patterns for inline matcher 1050 Markdown.buildInlinePatterns = function(d) { 1051 var patterns = []; 1052 1053 for ( var i in d ) { 1054 // __foo__ is reserved and not a pattern 1055 if ( i.match( /^__.*__$/) ) continue; 1056 var l = i.replace( /([\\.*+?|()\[\]{}])/g, "\\$1" ) 1057 .replace( /\n/, "\\n" ); 1058 patterns.push( i.length == 1 ? l : "(?:" + l + ")" ); 1059 } 1060 1061 patterns = patterns.join("|"); 1062 d.__patterns__ = patterns; 1063 //print("patterns:", uneval( patterns ) ); 1064 1065 var fn = d.__call__; 1066 d.__call__ = function(text, pattern) { 1067 if (pattern != undefined) { 1068 return fn.call(this, text, pattern); 1069 } 1070 else 1071 { 1072 return fn.call(this, text, patterns); 1073 } 1074 }; 1075 }; 1076 1077 Markdown.DialectHelpers = {}; 1078 Markdown.DialectHelpers.inline_until_char = function( text, want ) { 1079 var consumed = 0, 1080 nodes = []; 1081 1082 while ( true ) { 1083 if ( text[ consumed ] == want ) { 1084 // Found the character we were looking for 1085 consumed++; 1086 return [ consumed, nodes ]; 1087 } 1088 1089 if ( consumed >= text.length ) { 1090 // No closing char found. Abort. 1091 return null; 1092 } 1093 1094 var res = this.dialect.inline.__oneElement__.call(this, text.substr( consumed ) ); 1095 consumed += res[ 0 ]; 1096 // Add any returned nodes. 1097 nodes.push.apply( nodes, res.slice( 1 ) ); 1098 } 1099 } 1100 1101 // Helper function to make sub-classing a dialect easier 1102 Markdown.subclassDialect = function( d ) { 1103 function Block() {} 1104 Block.prototype = d.block; 1105 function Inline() {} 1106 Inline.prototype = d.inline; 1107 1108 return { block: new Block(), inline: new Inline() }; 1109 }; 1110 1111 Markdown.buildBlockOrder ( Markdown.dialects.Gruber.block ); 1112 Markdown.buildInlinePatterns( Markdown.dialects.Gruber.inline ); 1113 1114 Markdown.dialects.Maruku = Markdown.subclassDialect( Markdown.dialects.Gruber ); 1115 1116 Markdown.dialects.Maruku.processMetaHash = function processMetaHash( meta_string ) { 1117 var meta = split_meta_hash( meta_string ), 1118 attr = {}; 1119 1120 for ( var i = 0; i < meta.length; ++i ) { 1121 // id: #foo 1122 if ( /^#/.test( meta[ i ] ) ) { 1123 attr.id = meta[ i ].substring( 1 ); 1124 } 1125 // class: .foo 1126 else if ( /^\./.test( meta[ i ] ) ) { 1127 // if class already exists, append the new one 1128 if ( attr['class'] ) { 1129 attr['class'] = attr['class'] + meta[ i ].replace( /./, " " ); 1130 } 1131 else { 1132 attr['class'] = meta[ i ].substring( 1 ); 1133 } 1134 } 1135 // attribute: foo=bar 1136 else if ( /\=/.test( meta[ i ] ) ) { 1137 var s = meta[ i ].split( /\=/ ); 1138 attr[ s[ 0 ] ] = s[ 1 ]; 1139 } 1140 } 1141 1142 return attr; 1143 } 1144 1145 function split_meta_hash( meta_string ) { 1146 var meta = meta_string.split( "" ), 1147 parts = [ "" ], 1148 in_quotes = false; 1149 1150 while ( meta.length ) { 1151 var letter = meta.shift(); 1152 switch ( letter ) { 1153 case " " : 1154 // if we're in a quoted section, keep it 1155 if ( in_quotes ) { 1156 parts[ parts.length - 1 ] += letter; 1157 } 1158 // otherwise make a new part 1159 else { 1160 parts.push( "" ); 1161 } 1162 break; 1163 case "'" : 1164 case '"' : 1165 // reverse the quotes and move straight on 1166 in_quotes = !in_quotes; 1167 break; 1168 case "\\" : 1169 // shift off the next letter to be used straight away. 1170 // it was escaped so we'll keep it whatever it is 1171 letter = meta.shift(); 1172 default : 1173 parts[ parts.length - 1 ] += letter; 1174 break; 1175 } 1176 } 1177 1178 return parts; 1179 } 1180 1181 Markdown.dialects.Maruku.block.document_meta = function document_meta( block, next ) { 1182 // we're only interested in the first block 1183 if ( block.lineNumber > 1 ) return undefined; 1184 1185 // document_meta blocks consist of one or more lines of `Key: Value\n` 1186 if ( ! block.match( /^(?:\w+:.*\n)*\w+:.*$/ ) ) return undefined; 1187 1188 // make an attribute node if it doesn't exist 1189 if ( !extract_attr( this.tree ) ) { 1190 this.tree.splice( 1, 0, {} ); 1191 } 1192 1193 var pairs = block.split( /\n/ ); 1194 for ( p in pairs ) { 1195 var m = pairs[ p ].match( /(\w+):\s*(.*)$/ ), 1196 key = m[ 1 ].toLowerCase(), 1197 value = m[ 2 ]; 1198 1199 this.tree[ 1 ][ key ] = value; 1200 } 1201 1202 // document_meta produces no content! 1203 return []; 1204 }; 1205 1206 Markdown.dialects.Maruku.block.block_meta = function block_meta( block, next ) { 1207 // check if the last line of the block is an meta hash 1208 var m = block.match( /(^|\n) {0,3}\{:\s*((?:\\\}|[^\}])*)\s*\}$/ ); 1209 if ( !m ) return undefined; 1210 1211 // process the meta hash 1212 var attr = this.dialect.processMetaHash( m[ 2 ] ); 1213 1214 var hash; 1215 1216 // if we matched ^ then we need to apply meta to the previous block 1217 if ( m[ 1 ] === "" ) { 1218 var node = this.tree[ this.tree.length - 1 ]; 1219 hash = extract_attr( node ); 1220 1221 // if the node is a string (rather than JsonML), bail 1222 if ( typeof node === "string" ) return undefined; 1223 1224 // create the attribute hash if it doesn't exist 1225 if ( !hash ) { 1226 hash = {}; 1227 node.splice( 1, 0, hash ); 1228 } 1229 1230 // add the attributes in 1231 for ( a in attr ) { 1232 hash[ a ] = attr[ a ]; 1233 } 1234 1235 // return nothing so the meta hash is removed 1236 return []; 1237 } 1238 1239 // pull the meta hash off the block and process what's left 1240 var b = block.replace( /\n.*$/, "" ), 1241 result = this.processBlock( b, [] ); 1242 1243 // get or make the attributes hash 1244 hash = extract_attr( result[ 0 ] ); 1245 if ( !hash ) { 1246 hash = {}; 1247 result[ 0 ].splice( 1, 0, hash ); 1248 } 1249 1250 // attach the attributes to the block 1251 for ( a in attr ) { 1252 hash[ a ] = attr[ a ]; 1253 } 1254 1255 return result; 1256 }; 1257 1258 Markdown.dialects.Maruku.block.definition_list = function definition_list( block, next ) { 1259 // one or more terms followed by one or more definitions, in a single block 1260 var tight = /^((?:[^\s:].*\n)+):\s+([\s\S]+)$/, 1261 list = [ "dl" ], 1262 i; 1263 1264 // see if we're dealing with a tight or loose block 1265 if ( ( m = block.match( tight ) ) ) { 1266 // pull subsequent tight DL blocks out of `next` 1267 var blocks = [ block ]; 1268 while ( next.length && tight.exec( next[ 0 ] ) ) { 1269 blocks.push( next.shift() ); 1270 } 1271 1272 for ( var b = 0; b < blocks.length; ++b ) { 1273 var m = blocks[ b ].match( tight ), 1274 terms = m[ 1 ].replace( /\n$/, "" ).split( /\n/ ), 1275 defns = m[ 2 ].split( /\n:\s+/ ); 1276 1277 // print( uneval( m ) ); 1278 1279 for ( i = 0; i < terms.length; ++i ) { 1280 list.push( [ "dt", terms[ i ] ] ); 1281 } 1282 1283 for ( i = 0; i < defns.length; ++i ) { 1284 // run inline processing over the definition 1285 list.push( [ "dd" ].concat( this.processInline( defns[ i ].replace( /(\n)\s+/, "$1" ) ) ) ); 1286 } 1287 } 1288 } 1289 else { 1290 return undefined; 1291 } 1292 1293 return [ list ]; 1294 }; 1295 1296 Markdown.dialects.Maruku.inline[ "{:" ] = function inline_meta( text, matches, out ) { 1297 if ( !out.length ) { 1298 return [ 2, "{:" ]; 1299 } 1300 1301 // get the preceeding element 1302 var before = out[ out.length - 1 ]; 1303 1304 if ( typeof before === "string" ) { 1305 return [ 2, "{:" ]; 1306 } 1307 1308 // match a meta hash 1309 var m = text.match( /^\{:\s*((?:\\\}|[^\}])*)\s*\}/ ); 1310 1311 // no match, false alarm 1312 if ( !m ) { 1313 return [ 2, "{:" ]; 1314 } 1315 1316 // attach the attributes to the preceeding element 1317 var meta = this.dialect.processMetaHash( m[ 1 ] ), 1318 attr = extract_attr( before ); 1319 1320 if ( !attr ) { 1321 attr = {}; 1322 before.splice( 1, 0, attr ); 1323 } 1324 1325 for ( var k in meta ) { 1326 attr[ k ] = meta[ k ]; 1327 } 1328 1329 // cut out the string and replace it with nothing 1330 return [ m[ 0 ].length, "" ]; 1331 }; 1332 1333 Markdown.buildBlockOrder ( Markdown.dialects.Maruku.block ); 1334 Markdown.buildInlinePatterns( Markdown.dialects.Maruku.inline ); 1335 1336 var isArray = Array.isArray || function(obj) { 1337 return Object.prototype.toString.call(obj) == '[object Array]'; 1338 }; 1339 1340 var forEach; 1341 // Don't mess with Array.prototype. Its not friendly 1342 if ( Array.prototype.forEach ) { 1343 forEach = function( arr, cb, thisp ) { 1344 return arr.forEach( cb, thisp ); 1345 }; 1346 } 1347 else { 1348 forEach = function(arr, cb, thisp) { 1349 for (var i = 0; i < arr.length; i++) { 1350 cb.call(thisp || arr, arr[i], i, arr); 1351 } 1352 } 1353 } 1354 1355 function extract_attr( jsonml ) { 1356 return isArray(jsonml) 1357 && jsonml.length > 1 1358 && typeof jsonml[ 1 ] === "object" 1359 && !( isArray(jsonml[ 1 ]) ) 1360 ? jsonml[ 1 ] 1361 : undefined; 1362 } 1363 1364 1365 1366 /** 1367 * renderJsonML( jsonml[, options] ) -> String 1368 * - jsonml (Array): JsonML array to render to XML 1369 * - options (Object): options 1370 * 1371 * Converts the given JsonML into well-formed XML. 1372 * 1373 * The options currently understood are: 1374 * 1375 * - root (Boolean): wether or not the root node should be included in the 1376 * output, or just its children. The default `false` is to not include the 1377 * root itself. 1378 */ 1379 expose.renderJsonML = function( jsonml, options ) { 1380 options = options || {}; 1381 // include the root element in the rendered output? 1382 options.root = options.root || false; 1383 1384 var content = []; 1385 1386 if ( options.root ) { 1387 content.push( render_tree( jsonml ) ); 1388 } 1389 else { 1390 jsonml.shift(); // get rid of the tag 1391 if ( jsonml.length && typeof jsonml[ 0 ] === "object" && !( jsonml[ 0 ] instanceof Array ) ) { 1392 jsonml.shift(); // get rid of the attributes 1393 } 1394 1395 while ( jsonml.length ) { 1396 content.push( render_tree( jsonml.shift() ) ); 1397 } 1398 } 1399 1400 return content.join( "\n\n" ); 1401 }; 1402 1403 function escapeHTML( text ) { 1404 return text.replace( /&/g, "&" ) 1405 .replace( /</g, "<" ) 1406 .replace( />/g, ">" ) 1407 .replace( /"/g, """ ) 1408 .replace( /'/g, "'" ); 1409 } 1410 1411 function render_tree( jsonml ) { 1412 // basic case 1413 if ( typeof jsonml === "string" ) { 1414 return escapeHTML( jsonml ); 1415 } 1416 1417 var tag = jsonml.shift(), 1418 attributes = {}, 1419 content = []; 1420 1421 if ( jsonml.length && typeof jsonml[ 0 ] === "object" && !( jsonml[ 0 ] instanceof Array ) ) { 1422 attributes = jsonml.shift(); 1423 } 1424 1425 while ( jsonml.length ) { 1426 content.push( arguments.callee( jsonml.shift() ) ); 1427 } 1428 1429 var tag_attrs = ""; 1430 for ( var a in attributes ) { 1431 tag_attrs += " " + a + '="' + escapeHTML( attributes[ a ] ) + '"'; 1432 } 1433 1434 // be careful about adding whitespace here for inline elements 1435 if ( tag == "img" || tag == "br" || tag == "hr" ) { 1436 return "<"+ tag + tag_attrs + "/>"; 1437 } 1438 else { 1439 return "<"+ tag + tag_attrs + ">" + content.join( "" ) + "</" + tag + ">"; 1440 } 1441 } 1442 1443 function convert_tree_to_html( tree, references, options ) { 1444 var i; 1445 options = options || {}; 1446 1447 // shallow clone 1448 var jsonml = tree.slice( 0 ); 1449 1450 if (typeof options.preprocessTreeNode === "function") { 1451 jsonml = options.preprocessTreeNode(jsonml, references); 1452 } 1453 1454 // Clone attributes if they exist 1455 var attrs = extract_attr( jsonml ); 1456 if ( attrs ) { 1457 jsonml[ 1 ] = {}; 1458 for ( i in attrs ) { 1459 jsonml[ 1 ][ i ] = attrs[ i ]; 1460 } 1461 attrs = jsonml[ 1 ]; 1462 } 1463 1464 // basic case 1465 if ( typeof jsonml === "string" ) { 1466 return jsonml; 1467 } 1468 1469 // convert this node 1470 switch ( jsonml[ 0 ] ) { 1471 case "header": 1472 jsonml[ 0 ] = "h" + jsonml[ 1 ].level; 1473 delete jsonml[ 1 ].level; 1474 break; 1475 case "bulletlist": 1476 jsonml[ 0 ] = "ul"; 1477 break; 1478 case "numberlist": 1479 jsonml[ 0 ] = "ol"; 1480 break; 1481 case "listitem": 1482 jsonml[ 0 ] = "li"; 1483 break; 1484 case "para": 1485 jsonml[ 0 ] = "p"; 1486 break; 1487 case "markdown": 1488 jsonml[ 0 ] = "html"; 1489 if ( attrs ) delete attrs.references; 1490 break; 1491 case "code_block": 1492 jsonml[ 0 ] = "pre"; 1493 i = attrs ? 2 : 1; 1494 var code = [ "code" ]; 1495 code.push.apply( code, jsonml.splice( i ) ); 1496 jsonml[ i ] = code; 1497 break; 1498 case "inlinecode": 1499 jsonml[ 0 ] = "code"; 1500 break; 1501 case "img": 1502 jsonml[ 1 ].src = jsonml[ 1 ].href; 1503 delete jsonml[ 1 ].href; 1504 break; 1505 case "linebreak": 1506 jsonml[ 0 ] = "br"; 1507 break; 1508 case "link": 1509 jsonml[ 0 ] = "a"; 1510 break; 1511 case "link_ref": 1512 jsonml[ 0 ] = "a"; 1513 1514 // grab this ref and clean up the attribute node 1515 var ref = references[ attrs.ref ]; 1516 1517 // if the reference exists, make the link 1518 if ( ref ) { 1519 delete attrs.ref; 1520 1521 // add in the href and title, if present 1522 attrs.href = ref.href; 1523 if ( ref.title ) { 1524 attrs.title = ref.title; 1525 } 1526 1527 // get rid of the unneeded original text 1528 delete attrs.original; 1529 } 1530 // the reference doesn't exist, so revert to plain text 1531 else { 1532 return attrs.original; 1533 } 1534 break; 1535 case "img_ref": 1536 jsonml[ 0 ] = "img"; 1537 1538 // grab this ref and clean up the attribute node 1539 var ref = references[ attrs.ref ]; 1540 1541 // if the reference exists, make the link 1542 if ( ref ) { 1543 delete attrs.ref; 1544 1545 // add in the href and title, if present 1546 attrs.src = ref.href; 1547 if ( ref.title ) { 1548 attrs.title = ref.title; 1549 } 1550 1551 // get rid of the unneeded original text 1552 delete attrs.original; 1553 } 1554 // the reference doesn't exist, so revert to plain text 1555 else { 1556 return attrs.original; 1557 } 1558 break; 1559 } 1560 1561 // convert all the children 1562 i = 1; 1563 1564 // deal with the attribute node, if it exists 1565 if ( attrs ) { 1566 // if there are keys, skip over it 1567 for ( var key in jsonml[ 1 ] ) { 1568 i = 2; 1569 } 1570 // if there aren't, remove it 1571 if ( i === 1 ) { 1572 jsonml.splice( i, 1 ); 1573 } 1574 } 1575 1576 for ( ; i < jsonml.length; ++i ) { 1577 jsonml[ i ] = arguments.callee( jsonml[ i ], references, options ); 1578 } 1579 1580 return jsonml; 1581 } 1582 1583 1584 // merges adjacent text nodes into a single node 1585 function merge_text_nodes( jsonml ) { 1586 // skip the tag name and attribute hash 1587 var i = extract_attr( jsonml ) ? 2 : 1; 1588 1589 while ( i < jsonml.length ) { 1590 // if it's a string check the next item too 1591 if ( typeof jsonml[ i ] === "string" ) { 1592 if ( i + 1 < jsonml.length && typeof jsonml[ i + 1 ] === "string" ) { 1593 // merge the second string into the first and remove it 1594 jsonml[ i ] += jsonml.splice( i + 1, 1 )[ 0 ]; 1595 } 1596 else { 1597 ++i; 1598 } 1599 } 1600 // if it's not a string recurse 1601 else { 1602 arguments.callee( jsonml[ i ] ); 1603 ++i; 1604 } 1605 } 1606 } 1607 1608 } )( (function() { 1609 if ( typeof exports === "undefined" ) { 1610 window.markdown = {}; 1611 return window.markdown; 1612 } 1613 else { 1614 return exports; 1615 } 1616 } )() );