Browse Source

Use a common class for tokens

pull/82/head
Alex Kocharin 9 years ago
parent
commit
4aabd5592e
  1. 231
      lib/renderer.js
  2. 18
      lib/rules_block/blockquote.js
  3. 12
      lib/rules_block/code.js
  4. 14
      lib/rules_block/fence.js
  5. 28
      lib/rules_block/heading.js
  6. 10
      lib/rules_block/hr.js
  7. 12
      lib/rules_block/html_block.js
  8. 31
      lib/rules_block/lheading.js
  9. 46
      lib/rules_block/list.js
  10. 30
      lib/rules_block/paragraph.js
  11. 16
      lib/rules_block/state_block.js
  12. 91
      lib/rules_block/table.js
  13. 18
      lib/rules_core/block.js
  14. 71
      lib/rules_core/linkify.js
  15. 41
      lib/rules_inline/autolink.js
  16. 13
      lib/rules_inline/backticks.js
  17. 8
      lib/rules_inline/emphasis.js
  18. 5
      lib/rules_inline/escape.js
  19. 10
      lib/rules_inline/html_inline.js
  20. 17
      lib/rules_inline/image.js
  21. 20
      lib/rules_inline/link.js
  22. 15
      lib/rules_inline/newline.js
  23. 23
      lib/rules_inline/state_inline.js
  24. 4
      lib/rules_inline/strikethrough.js
  25. 54
      lib/token.js
  26. 8
      support/specsplit.js
  27. 27
      test/commonmark.js
  28. 136
      test/fixtures/markdown-it/tables.txt
  29. 4
      test/misc.js

231
lib/renderer.js

@ -15,30 +15,23 @@ var escapeHtml = require('./common/utils').escapeHtml;
////////////////////////////////////////////////////////////////////////////////
var rules = {};
var default_rules = {};
rules.blockquote_open = function () { return '<blockquote>\n'; };
rules.blockquote_close = function () { return '</blockquote>\n'; };
rules.code_block = function (tokens, idx /*, options, env */) {
default_rules.code_block = function (tokens, idx /*, options, env */) {
return '<pre><code>' + escapeHtml(tokens[idx].content) + '</code></pre>\n';
};
rules.code_inline = function (tokens, idx /*, options, env */) {
return '<code>' + escapeHtml(tokens[idx].content) + '</code>';
};
rules.fence = function (tokens, idx, options /*, env, self*/) {
default_rules.fence = function (tokens, idx, options /*, env, self*/) {
var token = tokens[idx];
var langClass = '';
var langPrefix = options.langPrefix;
var langName = '';
var highlighted;
if (token.params) {
langName = escapeHtml(unescapeAll(token.params.split(/\s+/g)[0]));
if (token.info) {
langName = escapeHtml(unescapeAll(token.info.split(/\s+/g)[0]));
langClass = ' class="' + langPrefix + langName + '"';
}
@ -55,123 +48,120 @@ rules.fence = function (tokens, idx, options /*, env, self*/) {
};
rules.heading_open = function (tokens, idx /*, options, env */) {
return '<h' + tokens[idx].hLevel + '>';
};
rules.heading_close = function (tokens, idx /*, options, env */) {
return '</h' + tokens[idx].hLevel + '>\n';
};
default_rules.image = function (tokens, idx, options, env, self) {
var i, token = tokens[idx],
alt = self.renderInlineAsText(tokens[idx].children, options, env);
rules.hr = function (tokens, idx, options /*, env */) {
return (options.xhtmlOut ? '<hr />\n' : '<hr>\n');
};
if (!token.attrs) { token.attrs = []; }
rules.bullet_list_open = function () { return '<ul>\n'; };
rules.bullet_list_close = function () { return '</ul>\n'; };
rules.list_item_open = function (tokens, idx /*, options, env */) {
var next = tokens[idx + 1];
if ((next.type === 'list_item_close') ||
(next.type === 'paragraph_open' && next.tight)) {
return '<li>';
}
return '<li>\n';
};
rules.list_item_close = function () { return '</li>\n'; };
rules.ordered_list_open = function (tokens, idx /*, options, env */) {
if (tokens[idx].order > 1) {
return '<ol start="' + tokens[idx].order + '">\n';
// Replace "alt" tag with children tags rendered as text
//
for (i = 0; i < token.attrs.length; i++) {
if (token.attrs[i][0] === 'alt') {
token.attrs[i][1] = alt;
}
}
return '<ol>\n';
};
rules.ordered_list_close = function () { return '</ol>\n'; };
rules.paragraph_open = function (tokens, idx /*, options, env */) {
return tokens[idx].tight ? '' : '<p>';
};
rules.paragraph_close = function (tokens, idx /*, options, env */) {
if (tokens[idx].tight === true) {
return tokens[idx + 1].type.slice(-5) === 'close' ? '' : '\n';
}
return '</p>\n';
return self.rules.default_token(tokens, idx, options, env, self);
};
rules.link_open = function (tokens, idx /*, options, env */) {
var title = tokens[idx].title ? (' title="' + escapeHtml(tokens[idx].title) + '"') : '';
var target = tokens[idx].target ? (' target="' + escapeHtml(tokens[idx].target) + '"') : '';
return '<a href="' + escapeHtml(tokens[idx].href) + '"' + title + target + '>';
default_rules.hardbreak = function (tokens, idx, options /*, env */) {
return options.xhtmlOut ? '<br />\n' : '<br>\n';
};
rules.link_close = function (/* tokens, idx, options, env */) {
return '</a>';
default_rules.softbreak = function (tokens, idx, options /*, env */) {
return options.breaks ? (options.xhtmlOut ? '<br />\n' : '<br>\n') : '\n';
};
rules.image = function (tokens, idx, options, env, self) {
var src = ' src="' + escapeHtml(tokens[idx].src) + '"';
var title = tokens[idx].title ? (' title="' + escapeHtml(tokens[idx].title) + '"') : '';
var alt = ' alt="' + self.renderInlineAsText(tokens[idx].tokens, options, env) + '"';
var suffix = options.xhtmlOut ? ' /' : '';
return '<img' + src + alt + title + suffix + '>';
default_rules.text = function (tokens, idx /*, options, env */) {
return escapeHtml(tokens[idx].content);
};
rules.table_open = function () { return '<table>\n'; };
rules.table_close = function () { return '</table>\n'; };
rules.thead_open = function () { return '<thead>\n'; };
rules.thead_close = function () { return '</thead>\n'; };
rules.tbody_open = function () { return '<tbody>\n'; };
rules.tbody_close = function () { return '</tbody>\n'; };
rules.tr_open = function () { return '<tr>'; };
rules.tr_close = function () { return '</tr>\n'; };
rules.th_open = function (tokens, idx /*, options, env */) {
if (tokens[idx].align) {
return '<th style="text-align:' + tokens[idx].align + '">';
}
return '<th>';
default_rules.html_block = function (tokens, idx /*, options, env */) {
return tokens[idx].content;
};
rules.th_close = function () { return '</th>'; };
rules.td_open = function (tokens, idx /*, options, env */) {
if (tokens[idx].align) {
return '<td style="text-align:' + tokens[idx].align + '">';
}
return '<td>';
default_rules.html_inline = function (tokens, idx /*, options, env */) {
return tokens[idx].content;
};
rules.td_close = function () { return '</td>'; };
rules.strong_open = function () { return '<strong>'; };
rules.strong_close = function () { return '</strong>'; };
rules.em_open = function () { return '<em>'; };
rules.em_close = function () { return '</em>'; };
default_rules.default_token = function (tokens, idx, options /*, env, self */) {
var i, l, nextToken,
result = '',
needLf = false,
token = tokens[idx];
// Insert a newline between hidden paragraph and subsequent opening
// block-level tag.
//
// For example, here we should insert a newline before blockquote:
// - a
// >
//
if (token.block && idx && tokens[idx - 1].hidden && token.nesting !== -1) {
result += '\n';
}
rules.s_open = function () { return '<s>'; };
rules.s_close = function () { return '</s>'; };
// Self-contained token with no tag name, e.g. `inline` or hidden paragraph.
//
// We should return content if it exists and an empty string otherwise.
//
if (!token.tag) {
result += (token.content ? escapeHtml(token.content) : '');
return result;
}
// Add token name, e.g. `<img`
//
result += (token.nesting === -1 ? '</' : '<') + token.tag;
rules.hardbreak = function (tokens, idx, options /*, env */) {
return options.xhtmlOut ? '<br />\n' : '<br>\n';
};
rules.softbreak = function (tokens, idx, options /*, env */) {
return options.breaks ? (options.xhtmlOut ? '<br />\n' : '<br>\n') : '\n';
};
// Encode attributes, e.g. `<img src="foo"`
// ^^^^^^^^^^
if (token.attrs) {
for (i = 0, l = token.attrs.length; i < l; i++) {
result += ' ' + escapeHtml(token.attrs[i][0]) + '="' + escapeHtml(token.attrs[i][1]) + '"';
}
}
// Add a slash for self-closing tags, e.g. `<img src="foo" /`
// ^^
if (token.nesting === 0 && options.xhtmlOut && token.content === null) {
result += ' /';
}
rules.text = function (tokens, idx /*, options, env */) {
return escapeHtml(tokens[idx].content);
};
// Check if we need to add a newline after this tag
//
if (token.block) {
needLf = true;
if (token.nesting === 1) {
if (idx + 1 < tokens.length) {
nextToken = tokens[idx + 1];
if (nextToken.type === 'inline' || nextToken.hidden) {
// Block-level tag containing an inline tag.
//
needLf = false;
} else if (nextToken.nesting === -1 && nextToken.tag === token.tag) {
// Opening tag + closing tag of the same type. E.g. `<li></li>`.
//
needLf = false;
}
}
}
}
// If it's self-contained token, add its contents + closing tag
//
if (token.nesting === 0 && token.content !== null) {
result += '>' + escapeHtml(token.content) + '</' + token.tag;
}
rules.html_block = function (tokens, idx /*, options, env */) {
return tokens[idx].content;
};
rules.html_inline = function (tokens, idx /*, options, env */) {
return tokens[idx].content;
result += needLf ? '>\n' : '>';
return result;
};
@ -210,7 +200,7 @@ function Renderer() {
* See [source code](https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js)
* for more details and examples.
**/
this.rules = assign({}, rules);
this.rules = assign({}, default_rules);
}
@ -223,11 +213,18 @@ function Renderer() {
* The same as [[Renderer.render]], but for single token of `inline` type.
**/
Renderer.prototype.renderInline = function (tokens, options, env) {
var result = '',
_rules = this.rules;
var type,
result = '',
rules = this.rules;
for (var i = 0, len = tokens.length; i < len; i++) {
result += _rules[tokens[i].type](tokens, i, options, env, this);
type = tokens[i].type;
if (typeof rules[type] !== 'undefined') {
result += rules[type](tokens, i, options, env, this);
} else {
result += rules.default_token(tokens, i, options, env);
}
}
return result;
@ -246,13 +243,13 @@ Renderer.prototype.renderInline = function (tokens, options, env) {
**/
Renderer.prototype.renderInlineAsText = function (tokens, options, env) {
var result = '',
_rules = this.rules;
rules = this.rules;
for (var i = 0, len = tokens.length; i < len; i++) {
if (tokens[i].type === 'text') {
result += _rules.text(tokens, i, options, env, this);
result += rules.text(tokens, i, options, env, this);
} else if (tokens[i].type === 'image') {
result += this.renderInlineAsText(tokens[i].tokens, options, env);
result += this.renderInlineAsText(tokens[i].children, options, env);
}
}
@ -270,15 +267,19 @@ Renderer.prototype.renderInlineAsText = function (tokens, options, env) {
* this method directly.
**/
Renderer.prototype.render = function (tokens, options, env) {
var i, len,
var i, len, type,
result = '',
_rules = this.rules;
rules = this.rules;
for (i = 0, len = tokens.length; i < len; i++) {
if (tokens[i].type === 'inline') {
type = tokens[i].type;
if (type === 'inline') {
result += this.renderInline(tokens[i].children, options, env);
} else if (typeof rules[type] !== 'undefined') {
result += rules[tokens[i].type](tokens, i, options, env, this);
} else {
result += _rules[tokens[i].type](tokens, i, options, env, this);
result += rules.default_token(tokens, i, options, env);
}
}

18
lib/rules_block/blockquote.js

@ -5,7 +5,7 @@
module.exports = function blockquote(state, startLine, endLine, silent) {
var nextLine, lastLineEmpty, oldTShift, oldBMarks, oldIndent, oldParentType, lines,
terminatorRules,
terminatorRules, token,
i, l, terminate,
pos = state.bMarks[startLine] + state.tShift[startLine],
max = state.eMarks[startLine];
@ -104,16 +104,14 @@ module.exports = function blockquote(state, startLine, endLine, silent) {
oldParentType = state.parentType;
state.parentType = 'blockquote';
state.tokens.push({
type: 'blockquote_open',
lines: lines = [ startLine, 0 ],
level: state.level++
});
token = state.push('blockquote_open', 'blockquote', 1);
token.map = lines = [ startLine, 0 ];
state.md.block.tokenize(state, startLine, nextLine);
state.tokens.push({
type: 'blockquote_close',
level: --state.level
});
token = state.push('blockquote_close', 'blockquote', -1);
state.parentType = oldParentType;
lines[1] = state.line;

12
lib/rules_block/code.js

@ -4,7 +4,7 @@
module.exports = function code(state, startLine, endLine/*, silent*/) {
var nextLine, last;
var nextLine, last, token;
if (state.tShift[startLine] - state.blkIndent < 4) { return false; }
@ -24,12 +24,10 @@ module.exports = function code(state, startLine, endLine/*, silent*/) {
}
state.line = nextLine;
state.tokens.push({
type: 'code_block',
content: state.getLines(startLine, last, 4 + state.blkIndent, true),
lines: [ startLine, state.line ],
level: state.level
});
token = state.push('code_block', 'code', 0);
token.content = state.getLines(startLine, last, 4 + state.blkIndent, true);
token.map = [ startLine, state.line ];
return true;
};

14
lib/rules_block/fence.js

@ -4,7 +4,7 @@
module.exports = function fence(state, startLine, endLine, silent) {
var marker, len, params, nextLine, mem,
var marker, len, params, nextLine, mem, token,
haveEndMarker = false,
pos = state.bMarks[startLine] + state.tShift[startLine],
max = state.eMarks[startLine];
@ -79,13 +79,11 @@ module.exports = function fence(state, startLine, endLine, silent) {
len = state.tShift[startLine];
state.line = nextLine + (haveEndMarker ? 1 : 0);
state.tokens.push({
type: 'fence',
params: params,
content: state.getLines(startLine + 1, nextLine, len, true),
lines: [ startLine, state.line ],
level: state.level
});
token = state.push('fence', 'code', 0);
token.info = params;
token.content = state.getLines(startLine + 1, nextLine, len, true);
token.map = [ startLine, state.line ];
return true;
};

28
lib/rules_block/heading.js

@ -4,7 +4,7 @@
module.exports = function heading(state, startLine, endLine, silent) {
var ch, level, tmp,
var ch, level, tmp, token,
pos = state.bMarks[startLine] + state.tShift[startLine],
max = state.eMarks[startLine];
@ -34,23 +34,15 @@ module.exports = function heading(state, startLine, endLine, silent) {
state.line = startLine + 1;
state.tokens.push({ type: 'heading_open',
hLevel: level,
lines: [ startLine, state.line ],
level: state.level
});
// only if header is not empty
if (pos < max) {
state.tokens.push({
type: 'inline',
content: state.src.slice(pos, max).trim(),
level: state.level + 1,
lines: [ startLine, state.line ],
children: []
});
}
state.tokens.push({ type: 'heading_close', hLevel: level, level: state.level });
token = state.push('heading_open', 'h' + String(level), 1);
token.map = [ startLine, state.line ];
token = state.push('inline', '', 0);
token.content = state.src.slice(pos, max).trim();
token.map = [ startLine, state.line ];
token.children = [];
token = state.push('heading_close', 'h' + String(level), -1);
return true;
};

10
lib/rules_block/hr.js

@ -4,7 +4,7 @@
module.exports = function hr(state, startLine, endLine, silent) {
var marker, cnt, ch,
var marker, cnt, ch, token,
pos = state.bMarks[startLine] + state.tShift[startLine],
max = state.eMarks[startLine];
@ -31,11 +31,9 @@ module.exports = function hr(state, startLine, endLine, silent) {
if (silent) { return true; }
state.line = startLine + 1;
state.tokens.push({
type: 'hr',
lines: [ startLine, state.line ],
level: state.level
});
token = state.push('hr', 'hr', 0);
token.map = [ startLine, state.line ];
return true;
};

12
lib/rules_block/html_block.js

@ -16,7 +16,7 @@ function isLetter(ch) {
}
module.exports = function html_block(state, startLine, endLine, silent) {
var ch, match, nextLine,
var ch, match, nextLine, token,
pos = state.bMarks[startLine],
max = state.eMarks[startLine],
shift = state.tShift[startLine];
@ -63,12 +63,10 @@ module.exports = function html_block(state, startLine, endLine, silent) {
}
state.line = nextLine;
state.tokens.push({
type: 'html_block',
level: state.level,
lines: [ startLine, state.line ],
content: state.getLines(startLine, nextLine, 0, true)
});
token = state.push('html_block', '', 0);
token.map = [ startLine, state.line ];
token.content = state.getLines(startLine, nextLine, 0, true);
return true;
};

31
lib/rules_block/lheading.js

@ -4,7 +4,7 @@
module.exports = function lheading(state, startLine, endLine/*, silent*/) {
var marker, pos, max,
var marker, pos, max, token, level,
next = startLine + 1;
if (next >= endLine) { return false; }
@ -32,24 +32,17 @@ module.exports = function lheading(state, startLine, endLine/*, silent*/) {
pos = state.bMarks[startLine] + state.tShift[startLine];
state.line = next + 1;
state.tokens.push({
type: 'heading_open',
hLevel: marker === 0x3D/* = */ ? 1 : 2,
lines: [ startLine, state.line ],
level: state.level
});
state.tokens.push({
type: 'inline',
content: state.src.slice(pos, state.eMarks[startLine]).trim(),
level: state.level + 1,
lines: [ startLine, state.line - 1 ],
children: []
});
state.tokens.push({
type: 'heading_close',
hLevel: marker === 0x3D/* = */ ? 1 : 2,
level: state.level
});
level = (marker === 0x3D/* = */ ? 1 : 2);
token = state.push('heading_open', 'h' + String(level), 1);
token.map = [ startLine, state.line ];
token = state.push('inline', '', 0);
token.content = state.src.slice(pos, state.eMarks[startLine]).trim();
token.map = [ startLine, state.line - 1 ];
token.children = [];
token = state.push('heading_close', 'h' + String(level), -1);
return true;
};

46
lib/rules_block/list.js

@ -73,8 +73,10 @@ function markTightParagraphs(state, idx) {
for (i = idx + 2, l = state.tokens.length - 2; i < l; i++) {
if (state.tokens[i].level === level && state.tokens[i].type === 'paragraph_open') {
state.tokens[i + 2].tight = true;
state.tokens[i].tight = true;
state.tokens[i + 2].tag = '';
state.tokens[i].tag = '';
state.tokens[i + 2].hidden = true;
state.tokens[i].hidden = true;
i += 2;
}
}
@ -102,6 +104,7 @@ module.exports = function list(state, startLine, endLine, silent) {
itemLines,
tight = true,
terminatorRules,
token,
i, l, terminate;
// Detect list type and position after marker
@ -126,21 +129,17 @@ module.exports = function list(state, startLine, endLine, silent) {
start = state.bMarks[startLine] + state.tShift[startLine];
markerValue = Number(state.src.substr(start, posAfterMarker - start - 1));
state.tokens.push({
type: 'ordered_list_open',
order: markerValue,
lines: listLines = [ startLine, 0 ],
level: state.level++
});
token = state.push('ordered_list_open', 'ol', 1);
if (markerValue > 1) {
token.attrs = [ [ 'start', markerValue ] ];
}
} else {
state.tokens.push({
type: 'bullet_list_open',
lines: listLines = [ startLine, 0 ],
level: state.level++
});
token = state.push('bullet_list_open', 'ul', 1);
}
token.map = listLines = [ startLine, 0 ];
//
// Iterate list items
//
@ -169,11 +168,8 @@ module.exports = function list(state, startLine, endLine, silent) {
indent = (posAfterMarker - state.bMarks[nextLine]) + indentAfterMarker;
// Run subparser & write tokens
state.tokens.push({
type: 'list_item_open',
lines: itemLines = [ startLine, 0 ],
level: state.level++
});
token = state.push('list_item_open', 'li', 1);
token.map = itemLines = [ startLine, 0 ];
oldIndent = state.blkIndent;
oldTight = state.tight;
@ -199,10 +195,7 @@ module.exports = function list(state, startLine, endLine, silent) {
state.tight = oldTight;
state.parentType = oldParentType;
state.tokens.push({
type: 'list_item_close',
level: --state.level
});
token = state.push('list_item_close', 'li', -1);
nextLine = startLine = state.line;
itemLines[1] = nextLine;
@ -242,10 +235,11 @@ module.exports = function list(state, startLine, endLine, silent) {
}
// Finilize list
state.tokens.push({
type: isOrdered ? 'ordered_list_close' : 'bullet_list_close',
level: --state.level
});
if (isOrdered) {
state.push('ordered_list_close', 'ol', -1);
} else {
state.push('bullet_list_close', 'ul', -1);
}
listLines[1] = nextLine;
state.line = nextLine;

30
lib/rules_block/paragraph.js

@ -4,7 +4,7 @@
module.exports = function paragraph(state, startLine/*, endLine*/) {
var endLine, content, terminate, i, l,
var endLine, content, terminate, i, l, token,
nextLine = startLine + 1,
terminatorRules;
@ -34,24 +34,16 @@ module.exports = function paragraph(state, startLine/*, endLine*/) {
content = state.getLines(startLine, nextLine, state.blkIndent, false).trim();
state.line = nextLine;
state.tokens.push({
type: 'paragraph_open',
tight: false,
lines: [ startLine, state.line ],
level: state.level
});
state.tokens.push({
type: 'inline',
content: content,
level: state.level + 1,
lines: [ startLine, state.line ],
children: []
});
state.tokens.push({
type: 'paragraph_close',
tight: false,
level: state.level
});
token = state.push('paragraph_open', 'p', 1);
token.map = [ startLine, state.line ];
token = state.push('inline', '', 0);
token.content = content;
token.map = [ startLine, state.line ];
token.children = [];
token = state.push('paragraph_close', 'p', -1);
return true;
};

16
lib/rules_block/state_block.js

@ -2,6 +2,8 @@
'use strict';
var Token = require('../token');
function StateBlock(src, md, env, tokens) {
var ch, s, start, pos, len, indent, indent_found;
@ -75,6 +77,20 @@ function StateBlock(src, md, env, tokens) {
this.lineMax = this.bMarks.length - 1; // don't count last fake line
}
// Push new token to "stream".
//
StateBlock.prototype.push = function (type, tag, nesting) {
var token = new Token(type, tag, nesting);
token.block = true;
if (nesting < 0) { this.level--; }
token.level = this.level;
if (nesting > 0) { this.level++; }
this.tokens.push(token);
return token;
};
StateBlock.prototype.isEmpty = function isEmpty(line) {
return this.bMarks[line] + this.tShift[line] >= this.eMarks[line];
};

91
lib/rules_block/table.js

@ -40,7 +40,7 @@ function escapedSplit(str) {
module.exports = function table(state, startLine, endLine, silent) {
var ch, lineText, pos, i, nextLine, rows,
var ch, lineText, pos, i, nextLine, rows, token,
aligns, t, tableLines, tbodyLines;
// should have at least three lines
@ -92,46 +92,35 @@ module.exports = function table(state, startLine, endLine, silent) {
if (aligns.length !== rows.length) { return false; }
if (silent) { return true; }
state.tokens.push({
type: 'table_open',
lines: tableLines = [ startLine, 0 ],
level: state.level++
});
state.tokens.push({
type: 'thead_open',
lines: [ startLine, startLine + 1 ],
level: state.level++
});
state.tokens.push({
type: 'tr_open',
lines: [ startLine, startLine + 1 ],
level: state.level++
});
token = state.push('table_open', 'table', 1);
token.map = tableLines = [ startLine, 0 ];
token = state.push('thead_open', 'thead', 1);
token.map = [ startLine, startLine + 1 ];
token = state.push('tr_open', 'tr', 1);
token.map = [ startLine, startLine + 1 ];
for (i = 0; i < rows.length; i++) {
state.tokens.push({
type: 'th_open',
align: aligns[i],
lines: [ startLine, startLine + 1 ],
level: state.level++
});
state.tokens.push({
type: 'inline',
content: rows[i].trim(),
lines: [ startLine, startLine + 1 ],
level: state.level,
children: []
});
state.tokens.push({ type: 'th_close', level: --state.level });
token = state.push('th_open', 'th', 1);
token.map = [ startLine, startLine + 1 ];
if (aligns[i]) {
token.attrs = [ [ 'style', 'text-align:' + aligns[i] ] ];
}
token = state.push('inline', '', 0);
token.content = rows[i].trim();
token.map = [ startLine, startLine + 1 ];
token.children = [];
token = state.push('th_close', 'th', -1);
}
state.tokens.push({ type: 'tr_close', level: --state.level });
state.tokens.push({ type: 'thead_close', level: --state.level });
state.tokens.push({
type: 'tbody_open',
lines: tbodyLines = [ startLine + 2, 0 ],
level: state.level++
});
token = state.push('tr_close', 'tr', -1);
token = state.push('thead_close', 'thead', -1);
token = state.push('tbody_open', 'tbody', 1);
token.map = tbodyLines = [ startLine + 2, 0 ];
for (nextLine = startLine + 2; nextLine < endLine; nextLine++) {
if (state.tShift[nextLine] < state.blkIndent) { break; }
@ -143,21 +132,23 @@ module.exports = function table(state, startLine, endLine, silent) {
// set number of columns to number of columns in header row
rows.length = aligns.length;
state.tokens.push({ type: 'tr_open', level: state.level++ });
token = state.push('tr_open', 'tr', 1);
for (i = 0; i < rows.length; i++) {
state.tokens.push({ type: 'td_open', align: aligns[i], level: state.level++ });
state.tokens.push({
type: 'inline',
content: rows[i] ? rows[i].trim() : '',
level: state.level,
children: []
});
state.tokens.push({ type: 'td_close', level: --state.level });
token = state.push('td_open', 'td', 1);
if (aligns[i]) {
token.attrs = [ [ 'style', 'text-align:' + aligns[i] ] ];
}
token = state.push('inline', '', 0);
token.content = rows[i] ? rows[i].trim() : '';
token.children = [];
token = state.push('td_close', 'td', -1);
}
state.tokens.push({ type: 'tr_close', level: --state.level });
token = state.push('tr_close', 'tr', -1);
}
state.tokens.push({ type: 'tbody_close', level: --state.level });
state.tokens.push({ type: 'table_close', level: --state.level });
token = state.push('tbody_close', 'tbody', -1);
token = state.push('table_close', 'table', -1);
tableLines[1] = tbodyLines[1] = nextLine;
state.line = nextLine;

18
lib/rules_core/block.js

@ -1,16 +1,20 @@
'use strict';
var Token = require('../token');
module.exports = function block(state) {
var token;
if (state.inlineMode) {
state.tokens.push({
type: 'inline',
content: state.src,
level: 0,
lines: [ 0, 1 ],
children: []
});
token = new Token('inline', '', 0);
token.content = state.src;
token.map = [ 0, 1 ];
token.children = [];
token.level = 0;
state.tokens.push(token);
} else {
state.md.block.parse(state.src, state.md, state.env, state.tokens);
}

71
lib/rules_core/linkify.js

@ -7,6 +7,7 @@
var arrayReplaceAt = require('../common/utils').arrayReplaceAt;
var normalizeLink = require('../common/utils').normalizeLink;
var Token = require('../token');
function isLinkOpen(str) {
@ -18,7 +19,7 @@ function isLinkClose(str) {
module.exports = function linkify(state) {
var i, j, l, tokens, token, nodes, ln, text, pos, lastPos, level, htmlLinkLevel,
var i, j, l, tokens, token, currentToken, nodes, ln, text, pos, lastPos, level, htmlLinkLevel,
blockTokens = state.tokens,
links;
@ -34,36 +35,36 @@ module.exports = function linkify(state) {
// We scan from the end, to keep position when new tags added.
// Use reversed logic in links start/end match
for (i = tokens.length - 1; i >= 0; i--) {
token = tokens[i];
currentToken = tokens[i];
// Skip content of markdown links
if (token.type === 'link_close') {
if (currentToken.type === 'link_close') {
i--;
while (tokens[i].level !== token.level && tokens[i].type !== 'link_open') {
while (tokens[i].level !== currentToken.level && tokens[i].type !== 'link_open') {
i--;
}
continue;
}
// Skip content of html tag links
if (token.type === 'html_inline') {
if (isLinkOpen(token.content) && htmlLinkLevel > 0) {
if (currentToken.type === 'html_inline') {
if (isLinkOpen(currentToken.content) && htmlLinkLevel > 0) {
htmlLinkLevel--;
}
if (isLinkClose(token.content)) {
if (isLinkClose(currentToken.content)) {
htmlLinkLevel++;
}
}
if (htmlLinkLevel > 0) { continue; }
if (token.type === 'text' && state.md.linkify.test(token.content)) {
if (currentToken.type === 'text' && state.md.linkify.test(currentToken.content)) {
text = token.content;
text = currentToken.content;
links = state.md.linkify.match(text);
// Now split string to nodes
nodes = [];
level = token.level;
level = currentToken.level;
lastPos = 0;
for (ln = 0; ln < links.length; ln++) {
@ -73,37 +74,33 @@ module.exports = function linkify(state) {
pos = links[ln].index;
if (pos > lastPos) {
level = level;
nodes.push({
type: 'text',
content: text.slice(lastPos, pos),
level: level
});
token = new Token('text', '', 0);
token.content = text.slice(lastPos, pos);
token.level = level;
nodes.push(token);
}
nodes.push({
type: 'link_open',
href: normalizeLink(links[ln].url),
target: '',
title: '',
level: level++
});
nodes.push({
type: 'text',
content: links[ln].text,
level: level
});
nodes.push({
type: 'link_close',
level: --level
});
token = new Token('link_open', 'a', 1);
token.attrs = [ [ 'href', normalizeLink(links[ln].url) ] ];
token.level = level++;
nodes.push(token);
token = new Token('text', '', 0);
token.content = links[ln].text;
token.level = level;
nodes.push(token);
token = new Token('link_close', 'a', -1);
token.level = --level;
nodes.push(token);
lastPos = links[ln].lastIndex;
}
if (lastPos < text.length) {
nodes.push({
type: 'text',
content: text.slice(lastPos),
level: level
});
token = new Token('text', '', 0);
token.content = text.slice(lastPos);
token.level = level;
nodes.push(token);
}
// replace current node

41
lib/rules_inline/autolink.js

@ -12,7 +12,8 @@ var AUTOLINK_RE = /^<([a-zA-Z.\-]{1,25}):([^<>\x00-\x20]*)>/;
module.exports = function autolink(state, silent) {
var tail, linkMatch, emailMatch, url, fullUrl, pos = state.pos;
var tail, linkMatch, emailMatch, url, fullUrl, token,
pos = state.pos;
if (state.src.charCodeAt(pos) !== 0x3C/* < */) { return false; }
@ -30,18 +31,13 @@ module.exports = function autolink(state, silent) {
if (!state.md.inline.validateLink(url)) { return false; }
if (!silent) {
state.push({
type: 'link_open',
href: fullUrl,
target: '',
level: state.level
});
state.push({
type: 'text',
content: url,
level: state.level + 1
});
state.push({ type: 'link_close', level: state.level });
token = state.push('link_open', 'a', 1);
token.attrs = [ [ 'href', fullUrl ] ];
token = state.push('text', '', 0);
token.content = url;
token = state.push('link_close', 'a', -1);
}
state.pos += linkMatch[0].length;
@ -58,18 +54,13 @@ module.exports = function autolink(state, silent) {
if (!state.md.inline.validateLink(fullUrl)) { return false; }
if (!silent) {
state.push({
type: 'link_open',
href: fullUrl,
target: '',
level: state.level
});
state.push({
type: 'text',
content: url,
level: state.level + 1
});
state.push({ type: 'link_close', level: state.level });
token = state.push('link_open', 'a', 1);
token.attrs = [ [ 'href', fullUrl ] ];
token = state.push('text', '', 0);
token.content = url;
token = state.push('link_close', 'a', -1);
}
state.pos += emailMatch[0].length;

13
lib/rules_inline/backticks.js

@ -3,7 +3,7 @@
'use strict';
module.exports = function backtick(state, silent) {
var start, max, marker, matchStart, matchEnd,
var start, max, marker, matchStart, matchEnd, token,
pos = state.pos,
ch = state.src.charCodeAt(pos);
@ -26,13 +26,10 @@ module.exports = function backtick(state, silent) {
if (matchEnd - matchStart === marker.length) {
if (!silent) {
state.push({
type: 'code_inline',
content: state.src.slice(pos, matchStart)
.replace(/[ \n]+/g, ' ')
.trim(),
level: state.level
});
token = state.push('code_inline', 'code', 0);
token.content = state.src.slice(pos, matchStart)
.replace(/[ \n]+/g, ' ')
.trim();
}
state.pos = matchEnd;
return true;

8
lib/rules_inline/emphasis.js

@ -147,15 +147,15 @@ module.exports = function emphasis(state, silent) {
// we have `startCount` starting and ending markers,
// now trying to serialize them into tokens
for (count = startCount; count > 1; count -= 2) {
state.push({ type: 'strong_open', level: state.level++ });
state.push('strong_open', 'strong', 1);
}
if (count % 2) { state.push({ type: 'em_open', level: state.level++ }); }
if (count % 2) { state.push('em_open', 'em', 1); }
state.md.inline.tokenize(state);
if (count % 2) { state.push({ type: 'em_close', level: --state.level }); }
if (count % 2) { state.push('em_close', 'em', -1); }
for (count = startCount; count > 1; count -= 2) {
state.push({ type: 'strong_close', level: --state.level });
state.push('strong_close', 'strong', -1);
}
state.pos = state.posMax + startCount;

5
lib/rules_inline/escape.js

@ -28,10 +28,7 @@ module.exports = function escape(state, silent) {
if (ch === 0x0A) {
if (!silent) {
state.push({
type: 'hardbreak',
level: state.level
});
state.push('hardbreak', 'br', 0);
}
pos++;

10
lib/rules_inline/html_inline.js

@ -14,7 +14,8 @@ function isLetter(ch) {
module.exports = function html_inline(state, silent) {
var ch, match, max, pos = state.pos;
var ch, match, max, token,
pos = state.pos;
if (!state.md.options.html) { return false; }
@ -38,11 +39,8 @@ module.exports = function html_inline(state, silent) {
if (!match) { return false; }
if (!silent) {
state.push({
type: 'html_inline',
content: state.src.slice(pos, pos + match[0].length),
level: state.level
});
token = state.push('html_inline', '', 0);
token.content = state.src.slice(pos, pos + match[0].length);
}
state.pos += match[0].length;
return true;

17
lib/rules_inline/image.js

@ -9,7 +9,8 @@ var normalizeReference = require('../common/utils').normalizeReference;
module.exports = function image(state, silent) {
var code,
var attrs,
code,
href,
label,
labelEnd,
@ -18,6 +19,7 @@ module.exports = function image(state, silent) {
ref,
res,
title,
token,
tokens,
start,
oldPos = state.pos,
@ -142,13 +144,12 @@ module.exports = function image(state, silent) {
);
newState.md.inline.tokenize(newState);
state.push({
type: 'image',
src: href,
title: title,
tokens: tokens,
level: state.level
});
token = state.push('image', 'img', 0);
token.attrs = attrs = [ [ 'src', href ], [ 'alt', '' ] ];
token.children = tokens;
if (title) {
attrs.push([ 'title', title ]);
}
}
state.pos = pos;

20
lib/rules_inline/link.js

@ -9,7 +9,8 @@ var normalizeReference = require('../common/utils').normalizeReference;
module.exports = function link(state, silent) {
var code,
var attrs,
code,
href,
label,
labelEnd,
@ -18,6 +19,7 @@ module.exports = function link(state, silent) {
res,
ref,
title,
token,
oldPos = state.pos,
max = state.posMax,
start = state.pos;
@ -132,15 +134,15 @@ module.exports = function link(state, silent) {
state.pos = labelStart;
state.posMax = labelEnd;
state.push({
type: 'link_open',
href: href,
target: '',
title: title,
level: state.level++
});
token = state.push('link_open', 'a', 1);
token.attrs = attrs = [ [ 'href', href ] ];
if (title) {
attrs.push([ 'title', title ]);
}
state.md.inline.tokenize(state);
state.push({ type: 'link_close', level: --state.level });
token = state.push('link_close', 'a', -1);
}
state.pos = pos;

15
lib/rules_inline/newline.js

@ -18,23 +18,14 @@ module.exports = function newline(state, silent) {
if (pmax >= 0 && state.pending.charCodeAt(pmax) === 0x20) {
if (pmax >= 1 && state.pending.charCodeAt(pmax - 1) === 0x20) {
state.pending = state.pending.replace(/ +$/, '');
state.push({
type: 'hardbreak',
level: state.level
});
state.push('hardbreak', 'br', 0);
} else {
state.pending = state.pending.slice(0, -1);
state.push({
type: 'softbreak',
level: state.level
});
state.push('softbreak', 'br', 0);
}
} else {
state.push({
type: 'softbreak',
level: state.level
});
state.push('softbreak', 'br', 0);
}
}

23
lib/rules_inline/state_inline.js

@ -3,6 +3,8 @@
'use strict';
var Token = require('../token');
function StateInline(src, md, env, outTokens) {
this.src = src;
this.env = env;
@ -23,25 +25,32 @@ function StateInline(src, md, env, outTokens) {
// Flush pending text
//
StateInline.prototype.pushPending = function () {
this.tokens.push({
type: 'text',
content: this.pending,
level: this.pendingLevel
});
var token = new Token('text', '', 0);
token.content = this.pending;
token.level = this.pendingLevel;
this.tokens.push(token);
this.pending = '';
return token;
};
// Push new token to "stream".
// If pending text exists - flush it as text token
//
StateInline.prototype.push = function (token) {
StateInline.prototype.push = function (type, tag, nesting) {
if (this.pending) {
this.pushPending();
}
this.tokens.push(token);
var token = new Token(type, tag, nesting);
if (nesting < 0) { this.level--; }
token.level = this.level;
if (nesting > 0) { this.level++; }
this.pendingLevel = this.level;
this.tokens.push(token);
return token;
};

4
lib/rules_inline/strikethrough.js

@ -120,9 +120,9 @@ module.exports = function strikethrough(state, silent) {
state.pos = start + 2;
// Earlier we checked !silent, but this implementation does not need it
state.push({ type: 's_open', level: state.level++ });
state.push('s_open', 's', 1);
state.md.inline.tokenize(state);
state.push({ type: 's_close', level: --state.level });
state.push('s_close', 's', -1);
state.pos = state.posMax + 2;
state.posMax = max;

54
lib/token.js

@ -0,0 +1,54 @@
// Token class
'use strict';
// Create a token
//
function Token(type, tag, nesting) {
// type of the token (string, e.g. "paragraph_open")
this.type = type;
// html tag name, e.g. "p"
this.tag = tag;
// html attributes, format:
// [ [ name1, value1 ], [ name2, value2 ] ]
this.attrs = null;
// source map info, format:
// [ line_begin, line_end ]
this.map = null;
// level change (number in {-1, 0, 1} set), where:
// - `1` means the tag is opening
// - `0` means the tag is self-closing
// - `-1` means the tag is closing
this.nesting = nesting;
// nesting level, same as `state.level`
this.level = 0;
// an array of child nodes (inline and img tokens)
this.children = null;
// in a case of self-closing tag (code, html, fence, etc.),
// it has contents of this tag
this.content = null;
// '*' or '_' for emphasis, fence string for fence, etc.
this.markup = '';
// fence infostring
this.info = null;
// block or inline-level token,
// used in renderer to calculate line breaks
this.block = false;
// if it's true, ignore this element when rendering
this.hidden = false;
}
module.exports = Token;

8
support/specsplit.js

@ -32,6 +32,12 @@ var options = cli.parseArgs();
////////////////////////////////////////////////////////////////////////////////
function normalize(text) {
return text.replace(/<blockquote>\n<\/blockquote>/g, '<blockquote></blockquote>');
}
////////////////////////////////////////////////////////////////////////////////
function readFile(filename, encoding, callback) {
if (options.file === '-') {
// read from stdin
@ -79,7 +85,7 @@ readFile(options.spec, 'utf8', function (error, input) {
};
try {
if (markdown.render(md) === html) {
if (markdown.render(md) === normalize(html)) {
good.push(result);
} else {
result.err = markdown.render(md);

27
test/commonmark.js

@ -1,14 +1,35 @@
'use strict';
var path = require('path');
var p = require('path');
var load = require('markdown-it-testgen').load;
var assert = require('chai').assert;
var generate = require('markdown-it-testgen');
function normalize(text) {
return text.replace(/<blockquote>\n<\/blockquote>/g, '<blockquote></blockquote>');
}
function generate(path, md) {
load(path, function (data) {
data.meta = data.meta || {};
var desc = data.meta.desc || p.relative(path, data.file);
(data.meta.skip ? describe.skip : describe)(desc, function () {
data.fixtures.forEach(function (fixture) {
it(fixture.header ? fixture.header : 'line ' + (fixture.first.range[0] - 1), function () {
assert.strictEqual(md.render(fixture.first.text), normalize(fixture.second.text));
});
});
});
});
}
describe('CommonMark', function () {
var md = require('../')('commonmark');
generate(path.join(__dirname, 'fixtures/commonmark/good.txt'), md);
generate(p.join(__dirname, 'fixtures/commonmark/good.txt'), md);
});

136
test/fixtures/markdown-it/tables.txt

@ -7,11 +7,20 @@ Simple:
.
<table>
<thead>
<tr><th>Heading 1</th><th>Heading 2</th></tr>
<tr>
<th>Heading 1</th>
<th>Heading 2</th>
</tr>
</thead>
<tbody>
<tr><td>Cell 1</td><td>Cell 2</td></tr>
<tr><td>Cell 3</td><td>Cell 4</td></tr>
<tr>
<td>Cell 1</td>
<td>Cell 2</td>
</tr>
<tr>
<td>Cell 3</td>
<td>Cell 4</td>
</tr>
</tbody>
</table>
.
@ -26,11 +35,26 @@ Column alignment:
.
<table>
<thead>
<tr><th style="text-align:center">Header 1</th><th style="text-align:right">Header 2</th><th style="text-align:left">Header 3</th><th>Header 4</th></tr>
<tr>
<th style="text-align:center">Header 1</th>
<th style="text-align:right">Header 2</th>
<th style="text-align:left">Header 3</th>
<th>Header 4</th>
</tr>
</thead>
<tbody>
<tr><td style="text-align:center">Cell 1</td><td style="text-align:right">Cell 2</td><td style="text-align:left">Cell 3</td><td>Cell 4</td></tr>
<tr><td style="text-align:center">Cell 5</td><td style="text-align:right">Cell 6</td><td style="text-align:left">Cell 7</td><td>Cell 8</td></tr>
<tr>
<td style="text-align:center">Cell 1</td>
<td style="text-align:right">Cell 2</td>
<td style="text-align:left">Cell 3</td>
<td>Cell 4</td>
</tr>
<tr>
<td style="text-align:center">Cell 5</td>
<td style="text-align:right">Cell 6</td>
<td style="text-align:left">Cell 7</td>
<td>Cell 8</td>
</tr>
</tbody>
</table>
.
@ -45,11 +69,26 @@ Cell 1 |Cell 2 |Cell 3 |Cell 4
.
<table>
<thead>
<tr><th style="text-align:left">Header 1</th><th style="text-align:center">Header 2</th><th style="text-align:right">Header 3</th><th>Header 4</th></tr>
<tr>
<th style="text-align:left">Header 1</th>
<th style="text-align:center">Header 2</th>
<th style="text-align:right">Header 3</th>
<th>Header 4</th>
</tr>
</thead>
<tbody>
<tr><td style="text-align:left">Cell 1</td><td style="text-align:center">Cell 2</td><td style="text-align:right">Cell 3</td><td>Cell 4</td></tr>
<tr><td style="text-align:left"><em>Cell 5</em></td><td style="text-align:center">Cell 6</td><td style="text-align:right">Cell 7</td><td>Cell 8</td></tr>
<tr>
<td style="text-align:left">Cell 1</td>
<td style="text-align:center">Cell 2</td>
<td style="text-align:right">Cell 3</td>
<td>Cell 4</td>
</tr>
<tr>
<td style="text-align:left"><em>Cell 5</em></td>
<td style="text-align:center">Cell 6</td>
<td style="text-align:right">Cell 7</td>
<td>Cell 8</td>
</tr>
</tbody>
</table>
.
@ -65,10 +104,16 @@ baz|baz
<blockquote>
<table>
<thead>
<tr><th>foo</th><th>foo</th></tr>
<tr>
<th>foo</th>
<th>foo</th>
</tr>
</thead>
<tbody>
<tr><td>bar</td><td>bar</td></tr>
<tr>
<td>bar</td>
<td>bar</td>
</tr>
</tbody>
</table>
</blockquote>
@ -87,10 +132,16 @@ Nested tables inside lists:
<li>
<table>
<thead>
<tr><th>foo</th><th>foo</th></tr>
<tr>
<th>foo</th>
<th>foo</th>
</tr>
</thead>
<tbody>
<tr><td>bar</td><td>bar</td></tr>
<tr>
<td>bar</td>
<td>bar</td>
</tr>
</tbody>
</table>
</li>
@ -106,10 +157,14 @@ Minimal one-column:
.
<table>
<thead>
<tr><th>foo</th></tr>
<tr>
<th>foo</th>
</tr>
</thead>
<tbody>
<tr><td>test2</td></tr>
<tr>
<td>test2</td>
</tr>
</tbody>
</table>
.
@ -211,10 +266,16 @@ bar|bar
<p>paragraph</p>
<table>
<thead>
<tr><th>foo</th><th>foo</th></tr>
<tr>
<th>foo</th>
<th>foo</th>
</tr>
</thead>
<tbody>
<tr><td>bar</td><td>bar</td></tr>
<tr>
<td>bar</td>
<td>bar</td>
</tr>
</tbody>
</table>
.
@ -228,10 +289,12 @@ paragraph
.
<table>
<thead>
<tr><th>foo</th><th>foo</th></tr>
<tr>
<th>foo</th>
<th>foo</th>
</tr>
</thead>
<tbody>
</tbody>
<tbody></tbody>
</table>
<p>paragraph</p>
.
@ -246,11 +309,20 @@ Delimiter escaping:
.
<table>
<thead>
<tr><th>Heading 1 \\</th><th>Heading 2</th></tr>
<tr>
<th>Heading 1 \\</th>
<th>Heading 2</th>
</tr>
</thead>
<tbody>
<tr><td>Cell|1|</td><td>Cell|2</td></tr>
<tr><td>| Cell\|3 \</td><td>Cell|4</td></tr>
<tr>
<td>Cell|1|</td>
<td>Cell|2</td>
</tr>
<tr>
<td>| Cell\|3 \</td>
<td>Cell|4</td>
</tr>
</tbody>
</table>
.
@ -265,11 +337,23 @@ Heading 1|Heading 2|Heading 3
.
<table>
<thead>
<tr><th>Heading 1</th><th>Heading 2</th><th>Heading 3</th></tr>
<tr>
<th>Heading 1</th>
<th>Heading 2</th>
<th>Heading 3</th>
</tr>
</thead>
<tbody>
<tr><td>1</td><td>2</td><td></td></tr>
<tr><td>1</td><td>2</td><td>3</td></tr>
<tr>
<td>1</td>
<td>2</td>
<td></td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
</tbody>
</table>
.

4
test/misc.js

@ -207,7 +207,7 @@ describe('Misc', function () {
it('Should render link target attr', function () {
var md = markdownit()
.use(require('markdown-it-for-inline'), 'target', 'link_open', function (tokens, idx) {
tokens[idx].target = '_blank';
tokens[idx].attrs.push([ 'target', '_blank' ]);
});
assert.strictEqual(md.render('[foo](bar)'), '<p><a href="bar" target="_blank">foo</a></p>\n');
@ -244,7 +244,7 @@ describe('maxNesting', function () {
var md = markdownit({ maxNesting: 2 });
assert.strictEqual(
md.render('>foo\n>>bar\n>>>baz'),
'<blockquote>\n<p>foo</p>\n<blockquote>\n</blockquote>\n</blockquote>\n'
'<blockquote>\n<p>foo</p>\n<blockquote></blockquote>\n</blockquote>\n'
);
});

Loading…
Cancel
Save