~bcsaller/juju-gui/update-reductions

« back to all changes in this revision

Viewing changes to app/assets/javascripts/gallery-markdown.js

  • Committer: Gary Poster
  • Date: 2012-12-20 21:59:21 UTC
  • mto: This revision was merged to the branch mainline in revision 293.
  • Revision ID: gary.poster@canonical.com-20121220215921-qw5hqm7a8uymvwfr
Correct release docs; improve review docs; make all files served locally, so https can work; update makefile to have better names for build artifacts and more correctly designate phony targets; reduce unnecessary duplication of file creation in repeated Makefile runs.

Show diffs side-by-side

added added

removed removed

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