Browse Source

Added token helpers & reorganized renderer

pull/82/head
Vitaly Puzrin 10 years ago
parent
commit
3730820945
  1. 180
      lib/renderer.js
  2. 2
      lib/rules_block/list.js
  3. 33
      lib/token.js
  4. 23
      test/token.js

180
lib/renderer.js

@ -23,16 +23,14 @@ default_rules.code_block = function (tokens, idx /*, options, env */) {
}; };
default_rules.fence = function (tokens, idx, options /*, env, self*/) { default_rules.fence = function (tokens, idx, options, env, self) {
var token = tokens[idx]; var token = tokens[idx],
var langClass = ''; langName = '',
var langPrefix = options.langPrefix; highlighted;
var langName = '';
var highlighted;
if (token.info) { if (token.info) {
langName = escapeHtml(unescapeAll(token.info.trim().split(/\s+/g)[0])); langName = unescapeAll(token.info.trim().split(/\s+/g)[0]);
langClass = ' class="' + langPrefix + langName + '"'; token.attrPush([ 'class', options.langPrefix + langName ]);
} }
if (options.highlight) { if (options.highlight) {
@ -41,28 +39,24 @@ default_rules.fence = function (tokens, idx, options /*, env, self*/) {
highlighted = escapeHtml(token.content); highlighted = escapeHtml(token.content);
} }
return '<pre><code' + self.renderAttrs(token.attrs) + '>'
return '<pre><code' + langClass + '>'
+ highlighted + highlighted
+ '</code></pre>\n'; + '</code></pre>\n';
}; };
default_rules.image = function (tokens, idx, options, env, self) { default_rules.image = function (tokens, idx, options, env, self) {
var i, token = tokens[idx], var token = tokens[idx];
alt = self.renderInlineAsText(tokens[idx].children, options, env);
if (!token.attrs) { token.attrs = []; }
// Replace "alt" tag with children tags rendered as text // "alt" attr MUST be set, even if empty. Because it's mandatory and
// should be placed on proper position for tests.
// //
for (i = 0; i < token.attrs.length; i++) { // Replace content with actual value
if (token.attrs[i][0] === 'alt') {
token.attrs[i][1] = alt;
}
}
return self.rules.default_token(tokens, idx, options, env, self); token.attrs[token.attrIndex('alt')][1] =
self.renderInlineAsText(token.children, options, env);
return self.renderToken(tokens, idx, options, env, self);
}; };
@ -87,12 +81,86 @@ default_rules.html_inline = function (tokens, idx /*, options, env */) {
}; };
default_rules.default_token = function (tokens, idx, options /*, env, self */) { /**
var i, l, nextToken, * new Renderer()
*
* Creates new [[Renderer]] instance and fill [[Renderer#rules]] with defaults.
**/
function Renderer() {
/**
* Renderer#rules -> Object
*
* Contains render rules for tokens. Can be updated and extended.
*
* ##### Example
*
* ```javascript
* var md = require('markdown-it')();
*
* md.renderer.rules.strong_open = function () { return '<b>'; };
* md.renderer.rules.strong_close = function () { return '</b>'; };
*
* var result = md.renderInline(...);
* ```
*
* Each rule is called as independed static function with fixed signature:
*
* ```javascript
* function my_token_render(tokens, idx, options, env, renderer) {
* // ...
* return renderedHTML;
* }
* ```
*
* See [source code](https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js)
* for more details and examples.
**/
this.rules = assign({}, default_rules);
}
/**
* Renderer.renderAttrs(attrs) -> String
* - attrs (Array): array of token attributes
*
* Render token attributes to string.
**/
Renderer.prototype.renderAttrs = function renderAttrs(attrs) {
var i, l, result;
if (!attrs) { return ''; }
result = '';
for (i = 0, l = attrs.length; i < l; i++) {
result += ' ' + escapeHtml(attrs[i][0]) + '="' + escapeHtml(attrs[i][1]) + '"';
}
return result;
};
/**
* Renderer.renderToken(tokens, idx, options) -> String
* - tokens (Array): list of tokens
* - idx (Numbed): token index to render
* - options (Object): params of parser instance
*
* Default token renderer. Can be overriden by custom function
* in [[Renderer#rules]].
**/
Renderer.prototype.renderToken = function renderToken(tokens, idx, options) {
var nextToken,
result = '', result = '',
needLf = false, needLf = false,
token = tokens[idx]; token = tokens[idx];
// Tight list paragraphs
if (token.hidden) {
return '';
}
// Insert a newline between hidden paragraph and subsequent opening // Insert a newline between hidden paragraph and subsequent opening
// block-level tag. // block-level tag.
// //
@ -100,39 +168,22 @@ default_rules.default_token = function (tokens, idx, options /*, env, self */) {
// - a // - a
// > // >
// //
if (token.block && idx && tokens[idx - 1].hidden && token.nesting !== -1) { if (token.block && token.nesting !== -1 && idx && tokens[idx - 1].hidden) {
result += '\n'; result += '\n';
} }
// 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` // Add token name, e.g. `<img`
//
result += (token.nesting === -1 ? '</' : '<') + token.tag; result += (token.nesting === -1 ? '</' : '<') + token.tag;
// Encode attributes, e.g. `<img src="foo"` // Encode attributes, e.g. `<img src="foo"`
// ^^^^^^^^^^ result += this.renderAttrs(token.attrs);
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" /` // Add a slash for self-closing tags, e.g. `<img src="foo" /`
// ^^
if (token.nesting === 0 && options.xhtmlOut && token.content === null) { if (token.nesting === 0 && options.xhtmlOut && token.content === null) {
result += ' /'; result += ' /';
} }
// Check if we need to add a newline after this tag // Check if we need to add a newline after this tag
//
if (token.block) { if (token.block) {
needLf = true; needLf = true;
@ -155,55 +206,16 @@ default_rules.default_token = function (tokens, idx, options /*, env, self */) {
} }
// If it's self-contained token, add its contents + closing tag // If it's self-contained token, add its contents + closing tag
//
if (token.nesting === 0 && token.content !== null) { if (token.nesting === 0 && token.content !== null) {
result += '>' + escapeHtml(token.content) + '</' + token.tag; result += '>' + escapeHtml(token.content) + '</' + token.tag;
} }
result += needLf ? '>\n' : '>'; result += needLf ? '>\n' : '>';
return result; return result;
}; };
/**
* new Renderer()
*
* Creates new [[Renderer]] instance and fill [[Renderer#rules]] with defaults.
**/
function Renderer() {
/**
* Renderer#rules -> Object
*
* Contains render rules for tokens. Can be updated and extended.
*
* ##### Example
*
* ```javascript
* var md = require('markdown-it')();
*
* md.renderer.rules.strong_open = function () { return '<b>'; };
* md.renderer.rules.strong_close = function () { return '</b>'; };
*
* var result = md.renderInline(...);
* ```
*
* Each rule is called as independed static function with fixed signature:
*
* ```javascript
* function my_token_render(tokens, idx, options, env, renderer) {
* // ...
* return renderedHTML;
* }
* ```
*
* See [source code](https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js)
* for more details and examples.
**/
this.rules = assign({}, default_rules);
}
/** /**
* Renderer.renderInline(tokens, options, env) -> String * Renderer.renderInline(tokens, options, env) -> String
* - tokens (Array): list on block tokens to renter * - tokens (Array): list on block tokens to renter
@ -223,7 +235,7 @@ Renderer.prototype.renderInline = function (tokens, options, env) {
if (typeof rules[type] !== 'undefined') { if (typeof rules[type] !== 'undefined') {
result += rules[type](tokens, i, options, env, this); result += rules[type](tokens, i, options, env, this);
} else { } else {
result += rules.default_token(tokens, i, options, env); result += this.renderToken(tokens, i, options, env);
} }
} }
@ -279,7 +291,7 @@ Renderer.prototype.render = function (tokens, options, env) {
} else if (typeof rules[type] !== 'undefined') { } else if (typeof rules[type] !== 'undefined') {
result += rules[tokens[i].type](tokens, i, options, env, this); result += rules[tokens[i].type](tokens, i, options, env, this);
} else { } else {
result += rules.default_token(tokens, i, options, env); result += this.renderToken(tokens, i, options, env);
} }
} }

2
lib/rules_block/list.js

@ -73,8 +73,6 @@ function markTightParagraphs(state, idx) {
for (i = idx + 2, l = state.tokens.length - 2; i < l; i++) { for (i = idx + 2, l = state.tokens.length - 2; i < l; i++) {
if (state.tokens[i].level === level && state.tokens[i].type === 'paragraph_open') { if (state.tokens[i].level === level && state.tokens[i].type === 'paragraph_open') {
state.tokens[i + 2].tag = '';
state.tokens[i].tag = '';
state.tokens[i + 2].hidden = true; state.tokens[i + 2].hidden = true;
state.tokens[i].hidden = true; state.tokens[i].hidden = true;
i += 2; i += 2;

33
lib/token.js

@ -106,4 +106,37 @@ function Token(type, tag, nesting) {
} }
/**
* Token.attrIndex(name) -> Number
*
* Search attribute index by name.
**/
Token.prototype.attrIndex = function attrIndex(name) {
var attrs, i, len;
if (!this.attrs) { return -1; }
attrs = this.attrs;
for (i = 0, len = attrs.length; i < len; i++) {
if (attrs[i][0] === name) { return i; }
}
return -1;
};
/**
* Token.attrPush(attrData)
*
* Add `[ name, value ]` attribute to list. Init attrs if necessary
**/
Token.prototype.attrPush = function attrPush(attrData) {
if (this.attrs) {
this.attrs.push(attrData);
} else {
this.attrs = [ attrData ];
}
};
module.exports = Token; module.exports = Token;

23
test/token.js

@ -0,0 +1,23 @@
'use strict';
var assert = require('chai').assert;
var Token = require('../lib/token');
describe('Token', function () {
it('attr', function () {
var t = new Token('test_token', 'tok', 1);
assert.strictEqual(t.attrs, null);
assert.equal(t.attrIndex('foo'), -1);
t.attrPush([ 'foo', 'bar' ]);
t.attrPush([ 'baz', 'bad' ]);
assert.equal(t.attrIndex('foo'), 0);
assert.equal(t.attrIndex('baz'), 1);
assert.equal(t.attrIndex('none'), -1);
});
});
Loading…
Cancel
Save