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      "![": function image( text ) {
   811  
   812        // Unlike images, alt text is plain text only. no other elements are
   813        // allowed in there
   814  
   815        // ![Alt text](/path/to/img.jpg "Optional title")
   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, "&amp;" )
  1405               .replace( /</g, "&lt;" )
  1406               .replace( />/g, "&gt;" )
  1407               .replace( /"/g, "&quot;" )
  1408               .replace( /'/g, "&#39;" );
  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  } )() );