commonmark
Advanced tools
Comparing version 0.17.0 to 0.17.1
@@ -0,1 +1,3 @@ | ||
"use strict"; | ||
var Benchmark = require('benchmark').Benchmark; | ||
@@ -6,3 +8,3 @@ var suite = new Benchmark.Suite(); | ||
// npm install showdown | ||
var Showdown = require('showdown').converter; | ||
var Showdown = require('showdown'); | ||
// npm install marked | ||
@@ -13,2 +15,6 @@ var marked = require('marked'); | ||
var showdown = new Showdown.converter(); | ||
var parser = new commonmark.Parser(); | ||
var renderer = new commonmark.HtmlRenderer(); | ||
var benchfile = process.argv[2]; | ||
@@ -19,16 +25,10 @@ | ||
suite.add('commonmark.js markdown->html', function() { | ||
"use strict"; | ||
var doc = new commonmark.Parser().parse(contents); | ||
var renderer = new commonmark.HtmlRenderer(); | ||
renderer.render(doc); | ||
renderer.render(parser.parse(contents)); | ||
}) | ||
.add('showdown.js markdown->html', function() { | ||
"use strict"; | ||
var converter = new Showdown(); | ||
converter.makeHtml(contents); | ||
showdown.makeHtml(contents); | ||
}) | ||
.add('marked.js markdown->html', function() { | ||
"use strict"; | ||
marked(contents); | ||
@@ -38,3 +38,2 @@ }) | ||
.add('markdown-it markdown->html', function() { | ||
"use strict"; | ||
markdownit.render(contents); | ||
@@ -44,5 +43,4 @@ }) | ||
.on('cycle', function(event) { | ||
"use strict"; | ||
console.log(String(event.target)); | ||
}) | ||
.run(); |
{ | ||
"name": "commonmark", | ||
"version": "0.17.0", | ||
"main": "dist/commonmark.js", | ||
"homepage": "https://github.com/jgm/commonmark.js", | ||
"description": "CommonMark parsing and rendering library", | ||
"license": "BSD3", | ||
"license": "BSD-2-Clause", | ||
"ignore": [ | ||
@@ -9,0 +8,0 @@ "**/.*", |
@@ -1,3 +0,27 @@ | ||
[0.17] | ||
[0.17.1] | ||
* Reorganized block parsing in a more modular way. There is now | ||
a `blocks` property of the parser that contains information | ||
about each type of block, which is used in parsing. Ultimately | ||
this will make it easier to extend the library, but the project | ||
is still only partially completed. | ||
* Code cleanup and simplification, with some performance optimizations. | ||
* Removed version from `bower.json`. Bower takes version from tags. | ||
* Initialize some properties at beginning of 'parse'. | ||
This fixes some mistakes in source position when the | ||
same Parser object was used to parse multiple times (#3). | ||
* Made parsing of backslash escapes a bit more efficient. | ||
* Removed refmap parameter of InlineParser.parse(). | ||
Instead, set the refmap property before running the parser. | ||
* Set `_string_content` to null after using, allowing it to be GCd. | ||
* Removed `_strings`; just append to `_string_content`. | ||
This gives better performance with v8. | ||
* Format benchmarks so that samples are linked. | ||
* Added in-browser benchmark. | ||
* Added API documentation to README. | ||
* xml renderer: use `sourcepos` attribute, not `data-sourcepos`. | ||
* Changed license to 2-clause BSD. Added clause for spec. | ||
[0.17.0] | ||
* Renamed `DocParser` -> `Parser`. | ||
@@ -4,0 +28,0 @@ Note: library users should update their code or it will break. |
@@ -6,7 +6,2 @@ "use strict"; | ||
var C_GREATERTHAN = 62; | ||
var C_NEWLINE = 10; | ||
var C_SPACE = 32; | ||
var C_OPEN_BRACKET = 91; | ||
var CODE_INDENT = 4; | ||
@@ -78,11 +73,2 @@ | ||
// destructively trip final blank lines in an array of strings | ||
var stripFinalBlankLines = function(lns) { | ||
var i = lns.length - 1; | ||
while (!reNonSpace.test(lns[i])) { | ||
lns.pop(); | ||
i--; | ||
} | ||
}; | ||
// DOC PARSER | ||
@@ -92,16 +78,2 @@ | ||
// Returns true if parent block can contain child block. | ||
var canContain = function(parent_type, child_type) { | ||
return ( parent_type === 'Document' || | ||
parent_type === 'BlockQuote' || | ||
parent_type === 'Item' || | ||
(parent_type === 'List' && child_type === 'Item') ); | ||
}; | ||
// Returns true if block type can accept lines of text. | ||
var acceptsLines = function(block_type) { | ||
return ( block_type === 'Paragraph' || | ||
block_type === 'CodeBlock' ); | ||
}; | ||
// Returns true if block ends with a blank line, descending if needed | ||
@@ -151,3 +123,3 @@ // into lists and sublists. | ||
var addLine = function(ln) { | ||
this.tip._strings.push(ln.slice(this.offset)); | ||
this.tip._string_content += ln.slice(this.offset) + '\n'; | ||
}; | ||
@@ -159,3 +131,3 @@ | ||
var addChild = function(tag, offset) { | ||
while (!canContain(this.tip.type, tag)) { | ||
while (!this.blocks[this.tip.type].canContain(tag)) { | ||
this.finalize(this.tip, this.lineNumber - 1); | ||
@@ -166,4 +138,3 @@ } | ||
var newBlock = new Node(tag, [[this.lineNumber, column_number], [0, 0]]); | ||
newBlock._strings = []; | ||
newBlock._string_content = null; | ||
newBlock._string_content = ''; | ||
this.tip.appendChild(newBlock); | ||
@@ -225,9 +196,11 @@ this.tip = newBlock; | ||
var closeUnmatchedBlocks = function() { | ||
// finalize any blocks not matched | ||
while (this.oldtip !== this.lastMatchedContainer) { | ||
var parent = this.oldtip._parent; | ||
this.finalize(this.oldtip, this.lineNumber - 1); | ||
this.oldtip = parent; | ||
if (!this.allClosed) { | ||
// finalize any blocks not matched | ||
while (this.oldtip !== this.lastMatchedContainer) { | ||
var parent = this.oldtip._parent; | ||
this.finalize(this.oldtip, this.lineNumber - 1); | ||
this.oldtip = parent; | ||
} | ||
this.allClosed = true; | ||
} | ||
return true; | ||
}; | ||
@@ -243,3 +216,5 @@ | ||
continue: function() { return 0; }, | ||
finalize: function() { return; } | ||
finalize: function() { return; }, | ||
canContain: function(t) { return (t !== 'Item'); }, | ||
acceptsLines: false | ||
}, | ||
@@ -269,11 +244,13 @@ List: { | ||
} | ||
} | ||
}, | ||
canContain: function(t) { return (t === 'Item'); }, | ||
acceptsLines: false | ||
}, | ||
BlockQuote: { | ||
continue: function(parser, container, next_nonspace) { | ||
continue: function(parser, container, nextNonspace) { | ||
var ln = parser.currentLine; | ||
if (next_nonspace - parser.offset <= 3 && | ||
ln.charCodeAt(next_nonspace) === C_GREATERTHAN) { | ||
parser.offset = next_nonspace + 1; | ||
if (ln.charCodeAt(parser.offset) === C_SPACE) { | ||
if (nextNonspace - parser.offset <= 3 && | ||
ln.charAt(nextNonspace) === '>') { | ||
parser.offset = nextNonspace + 1; | ||
if (ln.charAt(parser.offset) === ' ') { | ||
parser.offset++; | ||
@@ -286,9 +263,11 @@ } | ||
}, | ||
finalize: function() { return; } | ||
finalize: function() { return; }, | ||
canContain: function(t) { return (t !== 'Item'); }, | ||
acceptsLines: false | ||
}, | ||
Item: { | ||
continue: function(parser, container, next_nonspace) { | ||
if (next_nonspace === parser.currentLine.length) { // blank | ||
parser.offset = next_nonspace; | ||
} else if (next_nonspace - parser.offset >= | ||
continue: function(parser, container, nextNonspace) { | ||
if (nextNonspace === parser.currentLine.length) { // blank | ||
parser.offset = nextNonspace; | ||
} else if (nextNonspace - parser.offset >= | ||
container._listData.markerOffset + | ||
@@ -303,3 +282,5 @@ container._listData.padding) { | ||
}, | ||
finalize: function() { return; } | ||
finalize: function() { return; }, | ||
canContain: function(t) { return (t !== 'Item'); }, | ||
acceptsLines: false | ||
}, | ||
@@ -311,5 +292,5 @@ Header: { | ||
}, | ||
finalize: function(parser, block) { | ||
block._string_content = block._strings.join('\n'); | ||
} | ||
finalize: function() { return; }, | ||
canContain: function() { return false; }, | ||
acceptsLines: false | ||
}, | ||
@@ -321,12 +302,14 @@ HorizontalRule: { | ||
}, | ||
finalize: function() { return; } | ||
finalize: function() { return; }, | ||
canContain: function() { return false; }, | ||
acceptsLines: false | ||
}, | ||
CodeBlock: { | ||
continue: function(parser, container, next_nonspace) { | ||
continue: function(parser, container, nextNonspace) { | ||
var ln = parser.currentLine; | ||
var indent = next_nonspace - parser.offset; | ||
var indent = nextNonspace - parser.offset; | ||
if (container._isFenced) { // fenced | ||
var match = (indent <= 3 && | ||
ln.charAt(next_nonspace) === container._fenceChar && | ||
ln.slice(next_nonspace).match(reClosingCodeFence)); | ||
ln.charAt(nextNonspace) === container._fenceChar && | ||
ln.slice(nextNonspace).match(reClosingCodeFence)); | ||
if (match && match[0].length >= container._fenceLength) { | ||
@@ -339,3 +322,3 @@ // closing fence - we're at end of line, so we can return | ||
var i = container._fenceOffset; | ||
while (i > 0 && ln.charCodeAt(parser.offset) === C_SPACE) { | ||
while (i > 0 && ln.charAt(parser.offset) === ' ') { | ||
parser.offset++; | ||
@@ -348,4 +331,4 @@ i--; | ||
parser.offset += CODE_INDENT; | ||
} else if (next_nonspace === ln.length) { // blank | ||
parser.offset = next_nonspace; | ||
} else if (nextNonspace === ln.length) { // blank | ||
parser.offset = nextNonspace; | ||
} else { | ||
@@ -360,32 +343,37 @@ return 1; | ||
// first line becomes info string | ||
block.info = unescapeString(block._strings[0].trim()); | ||
if (block._strings.length === 1) { | ||
block._literal = ''; | ||
} else { | ||
block._literal = block._strings.slice(1).join('\n') + '\n'; | ||
} | ||
var content = block._string_content; | ||
var newlinePos = content.indexOf('\n'); | ||
var firstLine = content.slice(0, newlinePos); | ||
var rest = content.slice(newlinePos + 1); | ||
block.info = unescapeString(firstLine.trim()); | ||
block._literal = rest; | ||
} else { // indented | ||
stripFinalBlankLines(block._strings); | ||
block._literal = block._strings.join('\n') + '\n'; | ||
block._literal = block._string_content.replace(/(\n *)+$/, '\n'); | ||
} | ||
} | ||
block._string_content = null; // allow GC | ||
}, | ||
canContain: function() { return false; }, | ||
acceptsLines: true | ||
}, | ||
HtmlBlock: { | ||
continue: function(parser, container, next_nonspace) { | ||
return (next_nonspace === parser.currentLine.length ? 1 : 0); | ||
continue: function(parser, container, nextNonspace) { | ||
return (nextNonspace === parser.currentLine.length ? 1 : 0); | ||
}, | ||
finalize: function(parser, block) { | ||
block._literal = block._strings.join('\n'); | ||
} | ||
block._literal = block._string_content.replace(/(\n *)+$/, ''); | ||
block._string_content = null; // allow GC | ||
}, | ||
canContain: function() { return false; }, | ||
acceptsLines: true | ||
}, | ||
Paragraph: { | ||
continue: function(parser, container, next_nonspace) { | ||
return (next_nonspace === parser.currentLine.length ? 1 : 0); | ||
continue: function(parser, container, nextNonspace) { | ||
return (nextNonspace === parser.currentLine.length ? 1 : 0); | ||
}, | ||
finalize: function(parser, block) { | ||
var pos; | ||
block._string_content = block._strings.join('\n'); | ||
var hasReferenceDefs = false; | ||
// try parsing the beginning as link reference definitions: | ||
while (block._string_content.charCodeAt(0) === C_OPEN_BRACKET && | ||
while (block._string_content.charAt(0) === '[' && | ||
(pos = | ||
@@ -395,8 +383,10 @@ parser.inlineParser.parseReference(block._string_content, | ||
block._string_content = block._string_content.slice(pos); | ||
if (isBlank(block._string_content)) { | ||
block.unlink(); | ||
break; | ||
} | ||
hasReferenceDefs = true; | ||
} | ||
} | ||
if (hasReferenceDefs && isBlank(block._string_content)) { | ||
block.unlink(); | ||
} | ||
}, | ||
canContain: function() { return false; }, | ||
acceptsLines: true | ||
} | ||
@@ -410,3 +400,3 @@ }; | ||
var all_matched = true; | ||
var next_nonspace; | ||
var nextNonspace; | ||
var match; | ||
@@ -416,3 +406,3 @@ var data; | ||
var indent; | ||
var allClosed; | ||
var t; | ||
@@ -442,8 +432,8 @@ var container = this.doc; | ||
if (match === -1) { | ||
next_nonspace = ln.length; | ||
nextNonspace = ln.length; | ||
} else { | ||
next_nonspace = match; | ||
nextNonspace = match; | ||
} | ||
switch (this.blocks[container.type].continue(this, container, next_nonspace)) { | ||
switch (this.blocks[container.type].continue(this, container, nextNonspace)) { | ||
case 0: // we've matched, keep going | ||
@@ -455,3 +445,3 @@ break; | ||
case 2: // we've hit end of line for fenced code close and can return | ||
this.lastLineLength = ln.length - 1; // -1 for newline | ||
this.lastLineLength = ln.length; | ||
return; | ||
@@ -467,5 +457,5 @@ default: | ||
blank = next_nonspace === ln.length; | ||
blank = nextNonspace === ln.length; | ||
allClosed = (container === this.oldtip); | ||
this.allClosed = (container === this.oldtip); | ||
this.lastMatchedContainer = container; | ||
@@ -480,17 +470,18 @@ | ||
// adding children to the last matched container: | ||
while (true) { | ||
var t = container.type; | ||
while ((t = container.type) && !(t === 'CodeBlock' || t === 'HtmlBlock')) { | ||
match = matchAt(reNonSpace, ln, this.offset); | ||
if (match === -1) { | ||
next_nonspace = ln.length; | ||
nextNonspace = ln.length; | ||
blank = true; | ||
break; | ||
} else { | ||
next_nonspace = match; | ||
nextNonspace = match; | ||
blank = false; | ||
} | ||
indent = next_nonspace - this.offset; | ||
indent = nextNonspace - this.offset; | ||
if (t === 'CodeBlock' || t === 'HtmlBlock') { | ||
// this is a little performance optimization: | ||
if (indent < CODE_INDENT && !reMaybeSpecial.test(ln.slice(nextNonspace))) { | ||
this.offset = nextNonspace; | ||
break; | ||
@@ -500,47 +491,40 @@ } | ||
if (indent >= CODE_INDENT) { | ||
// indented code | ||
if (this.tip.type !== 'Paragraph' && !blank) { | ||
// indented code | ||
this.offset += CODE_INDENT; | ||
allClosed = allClosed || | ||
this.closeUnmatchedBlocks(); | ||
this.closeUnmatchedBlocks(); | ||
container = this.addChild('CodeBlock', this.offset); | ||
} else { | ||
// lazy paragraph continuation | ||
this.offset = nextNonspace; | ||
} | ||
break; | ||
} | ||
// this is a little performance optimization: | ||
if (matchAt(reMaybeSpecial, ln, next_nonspace) === -1) { | ||
break; | ||
} | ||
this.offset = next_nonspace; | ||
var cc = ln.charCodeAt(this.offset); | ||
if (cc === C_GREATERTHAN) { | ||
} else if (ln.charAt(nextNonspace) === '>') { | ||
// blockquote | ||
this.offset += 1; | ||
this.offset = nextNonspace + 1; | ||
// optional following space | ||
if (ln.charCodeAt(this.offset) === C_SPACE) { | ||
if (ln.charAt(this.offset) === ' ') { | ||
this.offset++; | ||
} | ||
allClosed = allClosed || this.closeUnmatchedBlocks(); | ||
container = this.addChild('BlockQuote', next_nonspace); | ||
this.closeUnmatchedBlocks(); | ||
container = this.addChild('BlockQuote', nextNonspace); | ||
} else if ((match = ln.slice(this.offset).match(reATXHeaderMarker))) { | ||
} else if ((match = ln.slice(nextNonspace).match(reATXHeaderMarker))) { | ||
// ATX header | ||
this.offset += match[0].length; | ||
allClosed = allClosed || this.closeUnmatchedBlocks(); | ||
container = this.addChild('Header', next_nonspace); | ||
this.offset = nextNonspace + match[0].length; | ||
this.closeUnmatchedBlocks(); | ||
container = this.addChild('Header', nextNonspace); | ||
container.level = match[0].trim().length; // number of #s | ||
// remove trailing ###s: | ||
container._strings = | ||
[ln.slice(this.offset).replace(/^ *#+ *$/, '').replace(/ +#+ *$/, '')]; | ||
container._string_content = | ||
ln.slice(this.offset).replace(/^ *#+ *$/, '').replace(/ +#+ *$/, ''); | ||
this.offset = ln.length; | ||
break; | ||
} else if ((match = ln.slice(this.offset).match(reCodeFence))) { | ||
} else if ((match = ln.slice(nextNonspace).match(reCodeFence))) { | ||
// fenced code block | ||
var fenceLength = match[0].length; | ||
allClosed = allClosed || this.closeUnmatchedBlocks(); | ||
container = this.addChild('CodeBlock', next_nonspace); | ||
this.closeUnmatchedBlocks(); | ||
container = this.addChild('CodeBlock', nextNonspace); | ||
container._isFenced = true; | ||
@@ -550,19 +534,20 @@ container._fenceLength = fenceLength; | ||
container._fenceOffset = indent; | ||
this.offset += fenceLength; | ||
this.offset = nextNonspace + fenceLength; | ||
} else if (matchAt(reHtmlBlockOpen, ln, this.offset) !== -1) { | ||
} else if (matchAt(reHtmlBlockOpen, ln, nextNonspace) !== -1) { | ||
// html block | ||
allClosed = allClosed || this.closeUnmatchedBlocks(); | ||
this.closeUnmatchedBlocks(); | ||
container = this.addChild('HtmlBlock', this.offset); | ||
this.offset -= indent; // back up so spaces are part of block | ||
// don't adjust this.offset; spaces are part of block | ||
break; | ||
} else if (t === 'Paragraph' && | ||
container._strings.length === 1 && | ||
((match = ln.slice(this.offset).match(reSetextHeaderLine)))) { | ||
(container._string_content.indexOf('\n') === | ||
container._string_content.length - 1) && | ||
((match = ln.slice(nextNonspace).match(reSetextHeaderLine)))) { | ||
// setext header line | ||
allClosed = allClosed || this.closeUnmatchedBlocks(); | ||
this.closeUnmatchedBlocks(); | ||
var header = new Node('Header', container.sourcepos); | ||
header.level = match[0][0] === '=' ? 1 : 2; | ||
header._strings = container._strings; | ||
header._string_content = container._string_content; | ||
container.insertAfter(header); | ||
@@ -575,13 +560,13 @@ container.unlink(); | ||
} else if (matchAt(reHrule, ln, this.offset) !== -1) { | ||
} else if (matchAt(reHrule, ln, nextNonspace) !== -1) { | ||
// hrule | ||
allClosed = allClosed || this.closeUnmatchedBlocks(); | ||
container = this.addChild('HorizontalRule', next_nonspace); | ||
this.offset = ln.length - 1; | ||
this.closeUnmatchedBlocks(); | ||
container = this.addChild('HorizontalRule', nextNonspace); | ||
this.offset = ln.length; | ||
break; | ||
} else if ((data = parseListMarker(ln, this.offset, indent))) { | ||
} else if ((data = parseListMarker(ln, nextNonspace, indent))) { | ||
// list item | ||
allClosed = allClosed || this.closeUnmatchedBlocks(); | ||
this.offset += data.padding; | ||
this.closeUnmatchedBlocks(); | ||
this.offset = nextNonspace + data.padding; | ||
@@ -591,3 +576,3 @@ // add the list if needed | ||
!(listsMatch(container._listData, data))) { | ||
container = this.addChild('List', next_nonspace); | ||
container = this.addChild('List', nextNonspace); | ||
container._listData = data; | ||
@@ -597,6 +582,7 @@ } | ||
// add the list item | ||
container = this.addChild('Item', next_nonspace); | ||
container = this.addChild('Item', nextNonspace); | ||
container._listData = data; | ||
} else { | ||
this.offset = nextNonspace; | ||
break; | ||
@@ -612,7 +598,5 @@ | ||
// First check for a lazy paragraph continuation: | ||
if (!allClosed && !blank && | ||
this.tip.type === 'Paragraph' && | ||
this.tip._strings.length > 0) { | ||
if (!this.allClosed && !blank && | ||
this.tip.type === 'Paragraph') { | ||
// lazy paragraph continuation | ||
this.addLine(ln); | ||
@@ -623,3 +607,3 @@ | ||
// finalize any blocks not matched | ||
allClosed = allClosed || this.closeUnmatchedBlocks(); | ||
this.closeUnmatchedBlocks(); | ||
if (blank && container.lastChild) { | ||
@@ -649,27 +633,12 @@ container.lastChild._lastLineBlank = true; | ||
switch (t) { | ||
case 'HtmlBlock': | ||
case 'CodeBlock': | ||
if (this.blocks[t].acceptsLines) { | ||
this.addLine(ln); | ||
break; | ||
case 'Header': | ||
case 'HorizontalRule': | ||
// nothing to do; we already added the contents. | ||
break; | ||
default: | ||
this.offset = next_nonspace; | ||
if (acceptsLines(t)) { | ||
this.addLine(ln); | ||
} else if (blank) { | ||
break; | ||
} else { | ||
// create paragraph container for line | ||
container = this.addChild('Paragraph', this.offset); | ||
this.addLine(ln); | ||
} | ||
} else if (this.offset < ln.length && !blank) { | ||
// create paragraph container for line | ||
container = this.addChild('Paragraph', this.offset); | ||
this.offset = nextNonspace; | ||
this.addLine(ln); | ||
} | ||
} | ||
this.lastLineLength = ln.length - 1; // -1 for newline | ||
this.lastLineLength = ln.length; | ||
}; | ||
@@ -685,3 +654,3 @@ | ||
block._open = false; | ||
block.sourcepos[1] = [lineNumber, this.lastLineLength + 1]; | ||
block.sourcepos[1] = [lineNumber, this.lastLineLength]; | ||
@@ -698,2 +667,3 @@ this.blocks[block.type].finalize(this, block); | ||
var walker = block.walker(); | ||
this.inlineParser.refmap = this.refmap; | ||
while ((event = walker.next())) { | ||
@@ -703,3 +673,3 @@ node = event.node; | ||
if (!event.entering && (t === 'Paragraph' || t === 'Header')) { | ||
this.inlineParser.parse(node, this.refmap); | ||
this.inlineParser.parse(node); | ||
} | ||
@@ -711,4 +681,2 @@ } | ||
var doc = new Node('Document', [[1, 1], [0, 0]]); | ||
doc._string_content = null; | ||
doc._strings = []; | ||
return doc; | ||
@@ -722,6 +690,11 @@ }; | ||
this.refmap = {}; | ||
this.lineNumber = 0; | ||
this.lastLineLength = 0; | ||
this.offset = 0; | ||
this.lastMatchedContainer = this.doc; | ||
this.currentLine = ""; | ||
if (this.options.time) { console.time("preparing input"); } | ||
var lines = input.split(reLineEnding); | ||
var len = lines.length; | ||
if (input.charCodeAt(input.length - 1) === C_NEWLINE) { | ||
if (input.charAt(input.length - 1) === '\n') { | ||
// ignore last blank line created by final newline | ||
@@ -756,2 +729,3 @@ len -= 1; | ||
offset: 0, | ||
allClosed: true, | ||
lastMatchedContainer: this.doc, | ||
@@ -758,0 +732,0 @@ refmap: {}, |
@@ -68,9 +68,9 @@ "use strict"; | ||
var reEscapable = new RegExp(ESCAPABLE); | ||
var reEscapable = new RegExp('^' + ESCAPABLE); | ||
var reEntityHere = new RegExp('^' + ENTITY, 'i'); | ||
var reTicks = new RegExp('`+'); | ||
var reTicks = /`+/; | ||
var reTicksHere = new RegExp('^`+'); | ||
var reTicksHere = /^`+/; | ||
@@ -144,10 +144,9 @@ var reEmailAutolink = /^<([a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)>/; | ||
var ticks = this.match(reTicksHere); | ||
if (!ticks) { | ||
if (ticks === null) { | ||
return 0; | ||
} | ||
var afterOpenTicks = this.pos; | ||
var foundCode = false; | ||
var matched; | ||
var node; | ||
while (!foundCode && (matched = this.match(reTicks))) { | ||
while ((matched = this.match(reTicks)) !== null) { | ||
if (matched === ticks) { | ||
@@ -170,3 +169,4 @@ node = new Node('Code'); | ||
// character, a hard line break (if the backslash is followed by a newline), | ||
// or a literal backslash to the block's children. | ||
// or a literal backslash to the block's children. Assumes current character | ||
// is a backslash. | ||
var parseBackslash = function(block) { | ||
@@ -176,18 +176,14 @@ var subj = this.subject, | ||
var node; | ||
if (subj.charCodeAt(pos) === C_BACKSLASH) { | ||
if (subj.charAt(pos + 1) === '\n') { | ||
this.pos = this.pos + 2; | ||
node = new Node('Hardbreak'); | ||
block.appendChild(node); | ||
} else if (reEscapable.test(subj.charAt(pos + 1))) { | ||
this.pos = this.pos + 2; | ||
block.appendChild(text(subj.charAt(pos + 1))); | ||
} else { | ||
this.pos++; | ||
block.appendChild(text('\\')); | ||
} | ||
return true; | ||
if (subj.charAt(pos + 1) === '\n') { | ||
this.pos = this.pos + 2; | ||
node = new Node('Hardbreak'); | ||
block.appendChild(node); | ||
} else if (reEscapable.test(subj.charAt(pos + 1))) { | ||
this.pos = this.pos + 2; | ||
block.appendChild(text(subj.charAt(pos + 1))); | ||
} else { | ||
return false; | ||
this.pos++; | ||
block.appendChild(text('\\')); | ||
} | ||
return true; | ||
}; | ||
@@ -224,10 +220,9 @@ | ||
var m = this.match(reHtmlTag); | ||
var node; | ||
if (m) { | ||
node = new Node('Html'); | ||
if (m === null) { | ||
return false; | ||
} else { | ||
var node = new Node('Html'); | ||
node._literal = m; | ||
block.appendChild(node); | ||
return true; | ||
} else { | ||
return false; | ||
} | ||
@@ -264,3 +259,3 @@ }; | ||
!(rePunctuation.test(char_after) && | ||
!(/\s/.test(char_before)) && | ||
!(reWhitespaceChar.test(char_before)) && | ||
!(rePunctuation.test(char_before))); | ||
@@ -427,7 +422,7 @@ right_flanking = numdelims > 0 && | ||
var title = this.match(reLinkTitle); | ||
if (title) { | ||
if (title === null) { | ||
return null; | ||
} else { | ||
// chop off quotes from title and unescape: | ||
return unescapeString(title.substr(1, title.length - 2)); | ||
} else { | ||
return null; | ||
} | ||
@@ -440,11 +435,11 @@ }; | ||
var res = this.match(reLinkDestinationBraces); | ||
if (res) { // chop off surrounding <..>: | ||
return normalizeURI(unescapeString(res.substr(1, res.length - 2))); | ||
} else { | ||
if (res === null) { | ||
res = this.match(reLinkDestination); | ||
if (res !== null) { | ||
if (res === null) { | ||
return null; | ||
} else { | ||
return normalizeURI(unescapeString(res)); | ||
} else { | ||
return null; | ||
} | ||
} else { // chop off surrounding <..>: | ||
return normalizeURI(unescapeString(res.substr(1, res.length - 2))); | ||
} | ||
@@ -798,9 +793,9 @@ }; | ||
// using refmap to resolve references. | ||
var parseInlines = function(block, refmap) { | ||
var parseInlines = function(block) { | ||
this.subject = block._string_content.trim(); | ||
this.pos = 0; | ||
this.refmap = refmap || {}; | ||
this.delimiters = null; | ||
while (this.parseInline(block)) { | ||
} | ||
block._string_content = null; // allow raw string to be garbage collected | ||
this.processEmphasis(block, null); | ||
@@ -807,0 +802,0 @@ }; |
@@ -75,3 +75,2 @@ "use strict"; | ||
this._open = true; | ||
this._strings = null; | ||
this._string_content = null; | ||
@@ -78,0 +77,0 @@ this._literal = null; |
@@ -128,3 +128,3 @@ "use strict"; | ||
if (pos) { | ||
attrs.push(['data-sourcepos', String(pos[0][0]) + ':' + | ||
attrs.push(['sourcepos', String(pos[0][0]) + ':' + | ||
String(pos[0][1]) + '-' + String(pos[1][0]) + ':' + | ||
@@ -131,0 +131,0 @@ String(pos[1][1])]); |
{ "name": "commonmark", | ||
"description": "a strongly specified, highly compatible variant of Markdown", | ||
"version": "0.17.0", | ||
"version": "0.17.1", | ||
"homepage": "http://commonmark.org", | ||
@@ -15,3 +15,3 @@ "keywords": | ||
"bugs": { "url": "https://github.com/jgm/commonmark.js/issues" }, | ||
"license": "BSD-3-Clause", | ||
"license": "BSD-2-Clause", | ||
"main": "./lib/index.js", | ||
@@ -18,0 +18,0 @@ "bin": { "commonmark": "./bin/commonmark" }, |
296
README.md
@@ -5,3 +5,3 @@ commonmark.js | ||
CommonMark is a rationalized version of Markdown syntax, | ||
with a [spec][the spec] and BSD3-licensed reference | ||
with a [spec][the spec] and BSD-licensed reference | ||
implementations in C and JavaScript. | ||
@@ -34,4 +34,5 @@ | ||
a standalone JavaScript file `js/dist/commonmark.js`, | ||
suitable for linking into a web page, or just fetch | ||
<http://spec.commonmark.org/js/commonmark.js>. | ||
suitable for linking into a web page, or fetch the latest | ||
from <http://spec.commonmark.org/js/commonmark.js>, or | ||
`bower install commonmark`. | ||
@@ -63,88 +64,162 @@ To run tests for the JavaScript library: | ||
var reader = new commonmark.Parser(); | ||
var writer = new commonmark.HtmlRenderer(); | ||
var parsed = reader.parse("Hello *world*"); // parsed is a 'Node' tree | ||
// transform parsed if you like... | ||
var result = writer.render(parsed); // result is a string | ||
``` js | ||
var reader = new commonmark.Parser(); | ||
var writer = new commonmark.HtmlRenderer(); | ||
var parsed = reader.parse("Hello *world*"); // parsed is a 'Node' tree | ||
// transform parsed if you like... | ||
var result = writer.render(parsed); // result is a String | ||
``` | ||
**A note on security:** | ||
THe library does not attempt to sanitize link attributes or | ||
raw HTML. If you use this library in applications that accept | ||
untrusted user input, you must run the output through an HTML | ||
sanitizer to protect against | ||
[XSS attacks](http://en.wikipedia.org/wiki/Cross-site_scripting). | ||
The constructors for `Parser` and `HtmlRenderer` take an optional | ||
`options` parameter: | ||
<!-- TODO | ||
``` js | ||
var writer = new commonmark.HtmlRenderer({sourcepos: true}); | ||
``` | ||
Public API | ||
---------- | ||
THe following options are currently supported: | ||
### Parser | ||
- `sourcepos`: if `true`, source position information for block-level | ||
elements will be rendered in the `data-sourcepos` attribute (for | ||
HTML) or the `sourcepos` attribute (for XML). | ||
constructor takes options param | ||
explain what can go into options | ||
It is also possible to override the `escape` and `softbreak` | ||
properties of a renderer. So, to make soft breaks render as hard | ||
breaks in HTML: | ||
public | ||
properties: options | ||
methods: parse | ||
``` js | ||
var writer = new commonmark.HtmlRenderer; | ||
writer.softbreak = "<br />"; | ||
``` | ||
?? should we add a filters [] option? | ||
a filter could be a function that transforms a node, | ||
and the parser could automatically run a walker with | ||
each filter | ||
To make them render as spaces: | ||
### Node | ||
``` js | ||
writer.softbreak = " "; | ||
``` | ||
getters: type, firstChild, lastChild,, | ||
next, prev, parent, sourcepos, isContainer | ||
getters+setters: literal, destination, title, | ||
info, level, listType, listTight, listStart, | ||
listDelimiter | ||
methods: appendChild(child), | ||
prependChild(child), | ||
unlink(), | ||
insertAfter(sibling), | ||
insertBefore(sibling), | ||
walker() | ||
To override `escape`, pass it a function with two parameters: | ||
the first is the string to be escaped, the second is a boolean | ||
that is `true` if the escaped string is to be included in an | ||
attribute. | ||
walker returns NodeWalker object with methods: | ||
resumeAt(node, entering) | ||
next() - returns an objcet with properties 'entering' and 'node' | ||
In addition to the `HtmlRenderer`, there is an `XmlRenderer`, which | ||
will produce an XML representation of the AST: | ||
examples: | ||
capitalize every string | ||
changing emphasis to ALL CAPS | ||
de-linkifying | ||
running all the code samples through a highlighter or other | ||
transform (svg?) | ||
``` js | ||
var writer = new commonmark.XmlRenderer({sourcepos: true}); | ||
``` | ||
?? would it be better to include NodeWalker in the API | ||
and have people do walker = new NodeWalker(node)? | ||
probably. | ||
The parser returns a Node. The following public properties are defined | ||
(those marked "read-only" have only a getter, not a setter): | ||
### HtmlRenderer | ||
- `type` (read-only): a String, one of | ||
`Text`, `Softbreak`, `Hardbreak`, `Emph`, `Strong`, | ||
`Html`, `Link`, `Image`, `Code`, `Document`, `Paragraph`, | ||
`BlockQuote`, `Item`, `List`, `Header`, `CodeBlock`, | ||
`HtmlBlock` `HorizontalRule`. | ||
- `firstChild` (read-only): a Node or null. | ||
- `lastChild` (read-only): a Node or null. | ||
- `next` (read-only): a Node or null. | ||
- `prev` (read-only): a Node or null. | ||
- `parent` (read-only): a Node or null. | ||
- `sourcepos` (read-only): an Array with the following form: | ||
`[[startline, startcolumn], [endline, endcolumn]]`. | ||
- `isContainer` (read-only): `true` if the Node can contain other | ||
Nodes as children. | ||
- `literal`: the literal String content of the node or null. | ||
- `destination`: link or image destination (String) or null. | ||
- `title`: link or image title (String) or null. | ||
- `info`: fenced code block info string (String) or null. | ||
- `level`: header level (Number). | ||
- `listType`: a String, either `Bullet` or `Ordered`. | ||
- `listTight`: `true` if list is tight. | ||
- `listStart`: a Number, the starting number of an ordered list. | ||
- `listDelimiter`: a String, either `)` or `.` for an ordered list. | ||
constructor takes options param | ||
document relevant options | ||
Nodes have the following public methods: | ||
methods: | ||
escapeXml(string, isAttribute) | ||
render(node) | ||
properties: | ||
options | ||
- `appendChild(child)`: Append a Node `child` to the end of the | ||
Node's children. | ||
- `prependChild(child)`: Prepend a Node `child` to the end of the | ||
Node's children. | ||
- `unlink()`: Remove the Node from the tree, severing its links | ||
with siblings and parents, and closing up gaps as needed. | ||
- `insertAfter(sibling)`: Insert a Node `sibling` after the Node. | ||
- `insertBefore(sibling)`: Insert a Node `sibling` before the Node. | ||
- `walker()`: Returns a NodeWalker that can be used to iterate through | ||
the Node tree rooted in the Node. | ||
The NodeWalker returned by `walker()` has two methods: | ||
### XmlRenderer | ||
- `next()`: Returns an object with properties `entering` (a boolean, | ||
which is `true` when we enter a Node from a parent or sibling, and | ||
`false` when we reenter it from a child). Returns `null` when | ||
we have finished walking the tree. | ||
- `resumeAt(node, entering)`: Resets the iterator to resume at the | ||
specified node and setting for `entering`. (Normally this isn't | ||
needed unless you do destructive updates to the Node tree.) | ||
constructor takes options param | ||
document relevant options | ||
Here is an example of the use of a NodeWalker to iterate through | ||
the tree, making transformations. This simple example converts | ||
the contents of all `Text` nodes to ALL CAPS: | ||
methods: | ||
escapeXml(string, isAttribute) | ||
render(node) | ||
properties: | ||
options | ||
``` js | ||
var walker = parsed.walker(); | ||
var event, node; | ||
--> | ||
while ((event = walker.next())) { | ||
node = event.node; | ||
if (event.entering && node.type === 'Text') { | ||
node.literal = node.literal.toUpperCase(); | ||
} | ||
} | ||
``` | ||
This more complex example converts emphasis to ALL CAPS: | ||
``` js | ||
var walker = parsed.walker(); | ||
var event, node; | ||
var inEmph = false; | ||
while ((event = walker.next())) { | ||
node = event.node; | ||
if (node.type === 'Emph') { | ||
if (event.entering) { | ||
inEmph = true; | ||
} else { | ||
inEmph = false; | ||
// add Emph node's children as siblings | ||
while (node.firstChild) { | ||
node.insertBefore(node.firstChild); | ||
} | ||
// remove the empty Emph node | ||
node.unlink() | ||
} | ||
} else if (inEmph && node.type === 'Text') { | ||
node.literal = node.literal.toUpperCase(); | ||
} | ||
} | ||
``` | ||
Exercises for the reader: write a transform to | ||
1. De-linkify a document, transforming links to regular text. | ||
2. Remove all raw HTML (`Html` and `HtmlBlock` nodes). | ||
3. Run fenced code blocks marked with a language name through | ||
a syntax highlighting library, replacing them with an `HtmlBlock` | ||
containing the highlighted code. | ||
4. Print warnings to the console for images without image | ||
descriptions or titles. | ||
A note on security | ||
------------------ | ||
The library does not attempt to sanitize link attributes or | ||
raw HTML. If you use this library in applications that accept | ||
untrusted user input, you must run the output through an HTML | ||
sanitizer to protect against | ||
[XSS attacks](http://en.wikipedia.org/wiki/Cross-site_scripting). | ||
Performance | ||
@@ -168,33 +243,60 @@ ----------- | ||
repository.) Results show a ratio of ops/second (higher is better) | ||
against the slowest implementation (always showdown). | ||
against showdown (which is usually the slowest implementation). | ||
| Sample |showdown |commonmark|marked |markdown-it| | ||
|------------------------|---------:|---------:|---------:|----------:| | ||
|README.md | 1| 3.3| 3.1| 4.3| | ||
|block-bq-flat.md | 1| 9.3| 13.6| 13.7| | ||
|block-bq-nested.md | 1| 12.5| 10.6| 13.2| | ||
|block-code.md | 1| 28.8| 64.7| 95.4| | ||
|block-fences.md | 1| 20.7| 67.9| 72.9| | ||
|block-heading.md | 1| 11.1| 11.8| 19.6| | ||
|block-hr.md | 1| 15.0| 16.0| 41.4| | ||
|block-html.md | 1| 8.2| 3.0| 15.9| | ||
|block-lheading.md | 1| 15.3| 19.2| 16.8| | ||
|block-list-flat.md | 1| 4.6| 4.4| 10.7| | ||
|block-list-nested.md | 1| 7.7| 6.0| 19.3| | ||
|block-ref-flat.md | 1| 2.0| 1.3| 1.7| | ||
|block-ref-nested.md | 1| 1.7| 1.6| 2.9| | ||
|inline-autolink.md | 1| 4.4| 7.4| 4.7| | ||
|inline-backticks.md | 1| 16.3| 14.3| 30.5| | ||
|inline-em-flat.md | 1| 4.1| 3.5| 9.2| | ||
|inline-em-nested.md | 1| 5.2| 5.1| 7.9| | ||
|inline-em-worst.md | 1| 5.7| 5.4| 3.7| | ||
|inline-entity.md | 1| 5.3| 10.5| 8.5| | ||
|inline-escape.md | 1| 4.8| 3.1| 13.1| | ||
|inline-html.md | 1| 3.6| 5.4| 5.1| | ||
|inline-links-flat.md | 1| 3.5| 4.2| 4.1| | ||
|inline-links-nested.md | 1| 4.1| 1.1| 1.6| | ||
|inline-newlines.md | 1| 7.6| 7.3| 15.3| | ||
|lorem1.md | 1| 8.9| 5.1| 5.7| | ||
|rawtabs.md | 1| 9.7| 10.6| 15.4| | ||
| Sample |showdown |commonmark|marked |markdown-it| | ||
|--------------------------|---------:|---------:|---------:|----------:| | ||
|[README.md] | 1| 2.9| 2.7| 3.7| | ||
|[block-bq-flat.md] | 1| 2.6| 3.6| 3.3| | ||
|[block-bq-nested.md] | 1| 8.1| 6.1| 7.4| | ||
|[block-code.md] | 1| 3.3| 7.3| 10.8| | ||
|[block-fences.md] | 1| 4.6| 13.7| 13.4| | ||
|[block-heading.md] | 1| 3.1| 3.1| 5.0| | ||
|[block-hr.md] | 1| 2.1| 2.0| 5.2| | ||
|[block-html.md] | 1| 1.8| 0.6| 3.3| | ||
|[block-lheading.md] | 1| 2.7| 3.0| 2.5| | ||
|[block-list-flat.md] | 1| 3.1| 2.8| 7.2| | ||
|[block-list-nested.md] | 1| 6.3| 4.9| 14.3| | ||
|[block-ref-flat.md] | 1| 0.6| 0.3| 0.4| | ||
|[block-ref-nested.md] | 1| 0.4| 0.4| 0.7| | ||
|[inline-autolink.md] | 1| 1.6| 2.7| 1.7| | ||
|[inline-backticks.md] | 1| 5.0| 3.9| 8.2| | ||
|[inline-em-flat.md] | 1| 0.7| 0.7| 1.5| | ||
|[inline-em-nested.md] | 1| 0.9| 0.8| 1.3| | ||
|[inline-em-worst.md] | 1| 1.1| 0.9| 0.6| | ||
|[inline-entity.md] | 1| 1.3| 2.6| 1.9| | ||
|[inline-escape.md] | 1| 1.6| 1| 4.3| | ||
|[inline-html.md] | 1| 1.8| 2.8| 2.6| | ||
|[inline-links-flat.md] | 1| 1.9| 1.9| 1.9| | ||
|[inline-links-nested.md] | 1| 1.4| 0.4| 0.5| | ||
|[inline-newlines.md] | 1| 1.2| 1.2| 2.4| | ||
|[lorem1.md] | 1| 5.4| 2.9| 3.2| | ||
|[rawtabs.md] | 1| 3.2| 3.1| 4.4| | ||
[block-html.md]: bench/samples/block-html.md | ||
[inline-links-nested.md]: bench/samples/inline-links-nested.md | ||
[inline-em-flat.md]: bench/samples/inline-em-flat.md | ||
[inline-autolink.md]: bench/samples/inline-autolink.md | ||
[inline-html.md]: bench/samples/inline-html.md | ||
[rawtabs.md]: bench/samples/rawtabs.md | ||
[inline-escape.md]: bench/samples/inline-escape.md | ||
[inline-em-worst.md]: bench/samples/inline-em-worst.md | ||
[block-list-nested.md]: bench/samples/block-list-nested.md | ||
[block-bq-nested.md]: bench/samples/block-bq-nested.md | ||
[block-bq-flat.md]: bench/samples/block-bq-flat.md | ||
[inline-newlines.md]: bench/samples/inline-newlines.md | ||
[block-ref-nested.md]: bench/samples/block-ref-nested.md | ||
[block-fences.md]: bench/samples/block-fences.md | ||
[lorem1.md]: bench/samples/lorem1.md | ||
[README.md]: bench/samples/README.md | ||
[inline-links-flat.md]: bench/samples/inline-links-flat.md | ||
[block-heading.md]: bench/samples/block-heading.md | ||
[inline-em-nested.md]: bench/samples/inline-em-nested.md | ||
[inline-entity.md]: bench/samples/inline-entity.md | ||
[block-list-flat.md]: bench/samples/block-list-flat.md | ||
[block-hr.md]: bench/samples/block-hr.md | ||
[block-lheading.md]: bench/samples/block-lheading.md | ||
[block-code.md]: bench/samples/block-code.md | ||
[inline-backticks.md]: bench/samples/inline-backticks.md | ||
[block-ref-flat.md]: bench/samples/block-ref-flat.md | ||
To generate this table, | ||
@@ -201,0 +303,0 @@ |
@@ -150,3 +150,4 @@ #!/usr/bin/env node | ||
for (var x = 1000; x <= 10000; x *= 10) { | ||
var x; | ||
for (x = 1000; x <= 10000; x *= 10) { | ||
cases.push( | ||
@@ -158,3 +159,3 @@ { name: 'nested strong emph ' + x + ' deep', | ||
} | ||
for (var x = 1000; x <= 10000; x *= 10) { | ||
for (x = 1000; x <= 10000; x *= 10) { | ||
cases.push( | ||
@@ -166,3 +167,3 @@ { name: 'nested brackets ' + x + ' deep', | ||
} | ||
for (var x = 1000; x <= 10000; x *= 10) { | ||
for (x = 1000; x <= 10000; x *= 10) { | ||
cases.push( | ||
@@ -169,0 +170,0 @@ { name: 'nested block quote ' + x + ' deep', |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
409718
312
58
4868