marked/docs/USING_PRO.md

570 lines
21 KiB
Markdown
Raw Normal View History

2018-02-25 16:37:11 -05:00
## Extending Marked
2018-02-25 16:01:22 -05:00
To champion the single-responsibility and open/closed principles, we have tried to make it relatively painless to extend Marked. If you are looking to add custom functionality, this is the place to start.
2018-02-25 16:01:22 -05:00
2020-04-20 10:50:09 -05:00
<h2 id="use">marked.use()</h2>
`marked.use(extension)` is the recommended way to extend Marked. The `extension` object can contain any [option](/using_advanced#options) available in Marked:
2020-04-20 10:50:09 -05:00
```js
const marked = require('marked');
marked.use({
pedantic: false,
gfm: true,
breaks: false,
sanitize: false,
smartLists: true,
smartypants: false,
xhtml: false
});
```
You can also supply multiple `extension` objects at once.
```
marked.use(myExtension, extension2, extension3);
\\ EQUIVALENT TO:
marked.use(myExtension);
marked.use(extension2);
marked.use(extension3);
```
2020-04-20 10:50:09 -05:00
All options will overwrite those previously set, except for the following options which will be merged with the existing framework and can be used to change or extend the functionality of Marked: `renderer`, `tokenizer`, `walkTokens`, and `extensions`.
2020-05-12 19:47:19 -05:00
* The `renderer` and `tokenizer` options are objects with functions that will be merged into the built-in `renderer` and `tokenizer` respectively.
* The `walkTokens` option is a function that will be called to post-process every token before rendering.
* The `extensions` option is an array of objects that can contain additional custom `renderer` and `tokenizer` steps that will execute before any of the default parsing logic occurs.
***
<h2>The Marked Pipeline</h2>
Before building your custom extensions, it is important to understand the components that Marked uses to translate from Markdown to HTML:
1) The user supplies Marked with an input string to be translated.
2) The `lexer` feeds segments of the input text string into each `tokenizer`, and from their output, generates a series of tokens in a nested tree structure.
3) Each `tokenizer` receives a segment of Markdown text and, if it matches a particular pattern, generates a token object containing any relevant information.
4) The `walkTokens` function will traverse every token in the tree and perform any final adjustments to the token contents.
4) The `parser` traverses the token tree and feeds each token into the appropriate `renderer`, and concatenates their outputs into the final HTML result.
5) Each `renderer` receives a token and manipulates its contents to generate a segment of HTML.
Marked provides methods of directly overriding the `renderer` and `tokenizer` for any existing token type, as well as inserting additional custom `renderer` and `tokenizer` functions to handle entirely custom syntax.
***
2020-04-20 10:50:09 -05:00
<h2 id="renderer">The Renderer : <code>renderer</code></h2>
2018-02-25 16:01:22 -05:00
The renderer defines the HTML output of a given token. If you supply a `renderer` object to the Marked options, it will be merged with the built-in renderer and any functions inside will override the default handling of that token type.
2018-02-25 16:37:11 -05:00
Calling `marked.use()` to override the same function multiple times will give priority to the version that was assigned *last*. Overriding functions can return `false` to fall back to the previous override in the sequence, or resume default behavior if all overrides return `false`. Returning any other value (including nothing) will prevent fallback behavior.
**Example:** Overriding output of the default `heading` token by adding an embedded anchor tag like on GitHub.
2018-02-25 16:37:11 -05:00
```js
// Create reference instance
2019-07-08 09:13:53 -05:00
const marked = require('marked');
2018-02-25 16:37:11 -05:00
// Override function
2020-04-19 00:38:19 -05:00
const renderer = {
heading(text, level) {
const escapedText = text.toLowerCase().replace(/[^\w]+/g, '-');
return `
<h${level}>
<a name="${escapedText}" class="anchor" href="#${escapedText}">
<span class="header-link"></span>
</a>
${text}
</h${level}>`;
}
2018-02-25 16:01:22 -05:00
};
2020-04-19 00:38:19 -05:00
marked.use({ renderer });
2018-02-25 16:37:11 -05:00
// Run marked
2020-04-19 00:38:19 -05:00
console.log(marked('# heading+'));
2018-02-25 16:01:22 -05:00
```
2018-02-25 16:37:11 -05:00
**Output:**
2018-02-25 16:01:22 -05:00
```html
<h1>
<a name="heading-" class="anchor" href="#heading-">
<span class="header-link"></span>
</a>
heading+
</h1>
```
### Block-level renderer methods
- <code>**code**(*string* code, *string* infostring, *boolean* escaped)</code>
- <code>**blockquote**(*string* quote)</code>
- <code>**html**(*string* html)</code>
- <code>**heading**(*string* text, *number* level, *string* raw, *Slugger* slugger)</code>
- <code>**hr**()</code>
- <code>**list**(*string* body, *boolean* ordered, *number* start)</code>
- <code>**listitem**(*string* text, *boolean* task, *boolean* checked)</code>
- <code>**checkbox**(*boolean* checked)</code>
- <code>**paragraph**(*string* text)</code>
- <code>**table**(*string* header, *string* body)</code>
- <code>**tablerow**(*string* content)</code>
- <code>**tablecell**(*string* content, *object* flags)</code>
### Inline-level renderer methods
- <code>**strong**(*string* text)</code>
- <code>**em**(*string* text)</code>
- <code>**codespan**(*string* code)</code>
- <code>**br**()</code>
- <code>**del**(*string* text)</code>
- <code>**link**(*string* href, *string* title, *string* text)</code>
- <code>**image**(*string* href, *string* title, *string* text)</code>
- <code>**text**(*string* text)</code>
2018-02-25 16:01:22 -05:00
2020-04-07 11:43:11 -05:00
`slugger` has the `slug` method to create a unique id from value:
```js
slugger.slug('foo') // foo
slugger.slug('foo') // foo-1
slugger.slug('foo') // foo-2
slugger.slug('foo 1') // foo-1-1
slugger.slug('foo-1') // foo-1-2
...
```
`slugger.slug` can also be called with the `dryrun` option for stateless operation:
2020-07-11 09:36:24 +01:00
```js
slugger.slug('foo') // foo
slugger.slug('foo') // foo-1
slugger.slug('foo') // foo-2
slugger.slug('foo', { dryrun: true }) // foo-3
slugger.slug('foo', { dryrun: true }) // foo-3
slugger.slug('foo') // foo-3
slugger.slug('foo') // foo-4
2020-07-11 09:36:24 +01:00
...
```
2018-02-25 16:01:22 -05:00
`flags` has the following properties:
```js
{
header: true || false,
align: 'center' || 'left' || 'right'
}
```
***
2018-02-25 16:01:22 -05:00
<h2 id="tokenizer">The Tokenizer : <code>tokenizer</code></h2>
2018-02-25 16:01:22 -05:00
The tokenizer defines how to turn markdown text into tokens. If you supply a `tokenizer` object to the Marked options, it will be merged with the built-in tokenizer and any functions inside will override the default handling of that token type.
2020-04-07 11:43:11 -05:00
Calling `marked.use()` to override the same function multiple times will give priority to the version that was assigned *last*. Overriding functions can return `false` to fall back to the previous override in the sequence, or resume default behavior if all overrides return `false`. Returning any other value (including nothing) will prevent fallback behavior.
2020-04-07 11:43:11 -05:00
**Example:** Overriding default `codespan` tokenizer to include LaTeX.
2020-04-07 11:43:11 -05:00
```js
// Create reference instance
const marked = require('marked');
// Override function
2020-04-19 00:38:19 -05:00
const tokenizer = {
codespan(src) {
const match = src.match(/\$+([^\$\n]+?)\$+/);
if (match) {
return {
type: 'codespan',
raw: match[0],
text: match[1].trim()
};
}
2020-04-19 01:38:41 -05:00
// return false to use original codespan tokenizer
return false;
2020-04-07 11:43:11 -05:00
}
};
2020-04-19 00:38:19 -05:00
marked.use({ tokenizer });
2020-04-07 11:43:11 -05:00
// Run marked
2020-04-19 00:38:19 -05:00
console.log(marked('$ latex code $\n\n` other code `'));
2020-04-07 11:43:11 -05:00
```
**Output:**
```html
2020-04-19 00:38:19 -05:00
<p><code>latex code</code></p>
<p><code>other code</code></p>
2020-04-07 11:43:11 -05:00
```
**NOTE**: This does not fully support latex, see issue [#1948](https://github.com/markedjs/marked/issues/1948).
2020-04-07 11:43:11 -05:00
### Block level tokenizer methods
- <code>**space**(*string* src)</code>
- <code>**code**(*string* src)</code>
- <code>**fences**(*string* src)</code>
- <code>**heading**(*string* src)</code>
- <code>**nptable**(*string* src)</code>
- <code>**hr**(*string* src)</code>
- <code>**blockquote**(*string* src)</code>
- <code>**list**(*string* src)</code>
- <code>**html**(*string* src)</code>
- <code>**def**(*string* src)</code>
- <code>**table**(*string* src)</code>
- <code>**lheading**(*string* src)</code>
- <code>**paragraph**(*string* src)</code>
- <code>**text**(*string* src)</code>
2020-04-07 11:43:11 -05:00
### Inline level tokenizer methods
- <code>**escape**(*string* src)</code>
- <code>**tag**(*string* src)</code>
- <code>**link**(*string* src)</code>
- <code>**reflink**(*string* src, *object* links)</code>
- <code>**emStrong**(*string* src, *string* maskedSrc, *string* prevChar)</code>
- <code>**codespan**(*string* src)</code>
- <code>**br**(*string* src)</code>
- <code>**del**(*string* src)</code>
- <code>**autolink**(*string* src, *function* mangle)</code>
- <code>**url**(*string* src, *function* mangle)</code>
- <code>**inlineText**(*string* src, *function* smartypants)</code>
2020-04-07 11:43:11 -05:00
2020-04-14 16:53:06 -05:00
`mangle` is a method that changes text to HTML character references:
```js
mangle('test@example.com')
// "&#x74;&#101;&#x73;&#116;&#x40;&#101;&#120;&#x61;&#x6d;&#112;&#108;&#101;&#46;&#x63;&#111;&#x6d;"
```
`smartypants` is a method that translates plain ASCII punctuation characters into “smart” typographic punctuation HTML entities:
https://daringfireball.net/projects/smartypants/
```js
smartypants('"this ... string"')
// "“this … string”"
```
***
<h2 id="walk-tokens">Walk Tokens : <code>walkTokens</code></h2>
2020-05-12 19:47:19 -05:00
The walkTokens function gets called with every token. Child tokens are called before moving on to sibling tokens. Each token is passed by reference so updates are persisted when passed to the parser. The return value of the function is ignored.
`marked.use()` can be called multiple times with different `walkTokens` functions. Each function will be called in order, starting with the function that was assigned *last*.
2020-05-12 19:47:19 -05:00
**Example:** Overriding heading tokens to start at h2.
```js
const marked = require('marked');
// Override function
const walkTokens = (token) => {
if (token.type === 'heading') {
token.depth += 1;
}
};
marked.use({ walkTokens });
// Run marked
console.log(marked('# heading 2\n\n## heading 3'));
```
**Output:**
```html
<h2 id="heading-2">heading 2</h2>
<h3 id="heading-3">heading 3</h3>
```
***
2018-02-25 16:37:11 -05:00
<h2 id="extensions">Custom Extensions : <code>extensions</code></h2>
2018-02-25 16:37:11 -05:00
You may supply an `extensions` array to the `options` object. This array can contain any number of `extension` objects, using the following properties:
2018-02-25 16:37:11 -05:00
<dl>
<dt><code><strong>name</strong></code></dt>
<dd>A string used to identify the token that will be handled by this extension.
If the name matches an existing extension name, or an existing method in the tokenizer/renderer methods listed above, they will override the previously assigned behavior, with priority on the extension that was assigned **last**. An extension can return `false` to fall back to the previous behavior.</dd>
<dt><code><strong>level</strong></code></dt>
<dd>A string to determine when to run the extension tokenizer. Must be equal to 'block' or 'inline'.
A **block-level** extension will be handled before any of the block-level tokenizer methods listed above, and generally consists of 'container-type' text (paragraphs, tables, blockquotes, etc.).
An **inline-level** extension will be handled inside each block-level token, before any of the inline-level tokenizer methods listed above. These generally consist of 'style-type' text (italics, bold, etc.).</dd>
<dt><code><strong>start</strong>(<i>string</i> src)</code></dt>
<dd>A function that returns the index of the next potential start of the custom token.
The index can be the result of a <code>src.match().index</code>, or even a simple <code>src.index()</code>. Marked will use this function to ensure that it does not skip over any text that should be part of the custom token.</dd>
<dt><code><strong>tokenizer</strong>(<i>string</i> src, <i>array</i> tokens)</code></dt>
<dd>A function that reads a string of Markdown text and returns a generated token. The <code>tokens</code> parameter contains the array of tokens that have been generated by the lexer up to that point, and can be used to access the previous token, for instance.
The return value should be an object with the following parameters:
<dl>
<dt><code><strong>type</strong></code></dt>
<dd>A string that matches the <code>name</code> parameter of the extension.</dd>
<dt><code><strong>raw</strong></code></dt>
<dd>A string containing all of the text that this token consumes from the source.</dd>
<dt><code><strong>tokens</strong> [optional]</code></dt>
<dd>An array of child tokens that will be traversed by the <code>walkTokens</code> function by default.</dd>
</dl>
The returned token can also contain any other custom parameters of your choice that your custom `renderer` might need to access.
The tokenizer function has access to the lexer in the `this` object, which can be used if any internal section of the string needs to be parsed further, such as in handling any inline syntax on the text within a block token. The key functions that may be useful include:
<dl>
<dt><code><strong>this.lexer.blockTokens</strong>(<i>string</i> text, <i>array</i> tokens)</code></dt>
<dd>This runs the block tokenizer functions (including any block-level extensions) on the provided text, and appends any resulting tokens onto the <code>tokens</code> array. The <code>tokens</code> array is also returned by the function. You might use this, for example, if your extension creates a "container"-type token (such as a blockquote) that can potentially include other block-level tokens inside.</dd>
<dt><code><strong>this.lexer.inline</strong>(<i>string</i> text, <i>array</i> tokens)</code></dt>
<dd>Parsing of inline-level tokens only occurs after all block-level tokens have been generated. This function adds <code>text</code> and <code>tokens</code> to a queue to be processed using inline-level tokenizers (including any inline-level extensions) at that later step. Tokens will be generated using the provided <code>text</code>, and any resulting tokens will be appended to the <code>tokens</code> array. Note that this function does **NOT** return anything since the inline processing cannot happen until the block-level processing is complete.</dd>
<dt><code><strong>this.lexer.inlineTokens</strong>(<i>string</i> text, <i>array</i> tokens)</code></dt>
<dd>Sometimes an inline-level token contains further nested inline tokens (such as a <pre><code>**strong**</code></pre> token inside of a <pre><code>### Heading</code></pre>). This runs the inline tokenizer functions (including any inline-level extensions) on the provided text, and appends any resulting tokens onto the <code>tokens</code> array. The <code>tokens</code> array is also returned by the function.</dd>
</dl>
</dd>
<dt><code><strong>renderer</strong>(<i>object</i> token)</code></dt>
<dd>A function that reads a token and returns the generated HTML output string.
The renderer function has access to the parser in the `this` object, which can be used if any part of the token needs needs to be parsed further, such as any child tokens. The key functions that may be useful include:
<dl>
<dt><code><strong>this.parser.parse</strong>(<i>array</i> tokens)</code></dt>
<dd>Runs the block renderer functions (including any extensions) on the provided array of tokens, and returns the resulting HTML string output. This is used to generate the HTML from any child block-level tokens, for example if your extension is a "container"-type token (such as a blockquote) that can potentially include other block-level tokens inside.</dd>
<dt><code><strong>this.parser.parseInline</strong>(<i>array</i> tokens)</code></dt>
<dd>Runs the inline renderer functions (including any extensions) on the provided array of tokens, and returns the resulting HTML string output. This is used to generate the HTML from any child inline-level tokens.</dd>
</dl>
</dd>
<dt><code><strong>childTokens</strong> [optional]</code></dt>
<dd>An array of strings that match the names of any token parameters that should be traversed by the <code>walkTokens</code> functions. For instance, if you want to use a second custom parameter to contain child tokens in addition to <code>tokens</code>, it could be listed here. If <code>childTokens</code> is provided, the <code>tokens</code> array will not be walked by default unless it is also included in the <code>childTokens</code> array.</dd>
</dl>
**Example:** Add a custom syntax to generate `<dl>` description lists.
``` js
const descriptionlist = {
name: 'descriptionList',
level: 'block', // Is this a block-level or inline-level tokenizer?
start(src) { return src.match(/:[^:\n]/)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
const rule = /^(?::[^:\n]+:[^:\n]*(?:\n|$))+/; // Regex for the complete token
const match = rule.exec(src);
if (match) {
const token = { // Token to generate
type: 'descriptionList', // Should match "name" above
raw: match[0], // Text to consume from the source
text: match[0].trim(), // Additional custom properties
tokens: [] // Array where child inline tokens will be generated
};
this.lexer.inline(token.text, token.tokens); // Queue this data to be processed for inline tokens
return token;
}
},
renderer(token) {
return `<dl>${this.parser.parseInline(token.tokens)}\n</dl>`; // parseInline to turn child tokens into HTML
}
};
const description = {
name: 'description',
level: 'inline', // Is this a block-level or inline-level tokenizer?
start(src) { return src.match(/:/)?.index; }, // Hint to Marked.js to stop and check for a match
tokenizer(src, tokens) {
const rule = /^:([^:\n]+):([^:\n]*)(?:\n|$)/; // Regex for the complete token
const match = rule.exec(src);
if (match) {
return { // Token to generate
type: 'description', // Should match "name" above
raw: match[0], // Text to consume from the source
dt: this.lexer.inlineTokens(match[1].trim()), // Additional custom properties, including
dd: this.lexer.inlineTokens(match[2].trim()) // any further-nested inline tokens
};
}
},
renderer(token) {
return `\n<dt>${this.parser.parseInline(token.dt)}</dt><dd>${this.parser.parseInline(token.dd)}</dd>`;
},
childTokens: ['dt', 'dd'], // Any child tokens to be visited by walkTokens
walkTokens(token) { // Post-processing on the completed token tree
if (token.type === 'strong') {
token.text += ' walked';
}
}
};
marked.use({ extensions: [descriptionlist, description] });
\\ EQUIVALENT TO:
marked.use({extensions: [descriptionList] });
marked.use({extensions: [description] });
console.log(marked('A Description List:\n'
+ ': Topic 1 : Description 1\n'
+ ': **Topic 2** : *Description 2*'));
```
**Output**
``` bash
<p>A Description List:</p>
<dl>
<dt>Topic 1</dt><dd>Description 1</dd>
<dt><strong>Topic 2 walked</strong></dt><dd><em>Description 2</em></dd>
</dl>
```
2018-02-25 16:37:11 -05:00
***
<h2 id="lexer">The Lexer</h2>
The lexer takes a markdown string and calls the tokenizer functions.
<h2 id="parser">The Parser</h2>
The parser takes tokens as input and calls the renderer functions.
<h2 id="extend">Access to Lexer and Parser</h2>
2018-02-25 16:01:22 -05:00
2020-04-02 01:05:04 -05:00
You also have direct access to the lexer and parser if you so desire.
2018-02-25 16:01:22 -05:00
``` js
2020-04-02 01:05:04 -05:00
const tokens = marked.lexer(markdown, options);
2019-11-06 16:03:39 -06:00
console.log(marked.parser(tokens, options));
2018-02-25 16:01:22 -05:00
```
``` js
2019-07-08 09:13:53 -05:00
const lexer = new marked.Lexer(options);
2020-04-02 01:05:04 -05:00
const tokens = lexer.lex(markdown);
2018-02-25 16:01:22 -05:00
console.log(tokens);
2020-04-15 00:02:18 -05:00
console.log(lexer.tokenizer.rules.block); // block level rules used
console.log(lexer.tokenizer.rules.inline); // inline level rules used
console.log(marked.Lexer.rules.block); // all block level rules
console.log(marked.Lexer.rules.inline); // all inline level rules
2018-02-25 16:01:22 -05:00
```
``` bash
$ node
2020-04-02 01:05:04 -05:00
> require('marked').lexer('> I am using marked.')
[
{
type: "blockquote",
raw: "> I am using marked.",
tokens: [
{
type: "paragraph",
raw: "I am using marked.",
text: "I am using marked.",
tokens: [
{
type: "text",
raw: "I am using marked.",
text: "I am using marked."
}
]
}
]
},
links: {}
]
```
2020-04-02 01:05:04 -05:00
The Lexer builds an array of tokens, which will be passed to the Parser.
The Parser processes each token in the token array:
``` js
const marked = require('marked');
const md = `
# heading
[link][1]
[1]: #heading "heading"
`;
2020-04-02 01:05:04 -05:00
const tokens = marked.lexer(md);
console.log(tokens);
const html = marked.parser(tokens);
console.log(html);
```
``` bash
2020-04-02 01:05:04 -05:00
[
{
type: "heading",
raw: " # heading\n\n",
depth: 1,
text: "heading",
tokens: [
{
type: "text",
raw: "heading",
text: "heading"
}
]
},
{
type: "paragraph",
raw: " [link][1]",
text: " [link][1]",
tokens: [
{
type: "text",
raw: " ",
text: " "
},
{
type: "link",
raw: "[link][1]",
text: "link",
href: "#heading",
title: "heading",
tokens: [
{
type: "text",
raw: "link",
text: "link"
}
]
}
]
},
{
type: "space",
raw: "\n\n"
},
links: {
"1": {
href: "#heading",
title: "heading"
}
}
]
<h1 id="heading">heading</h1>
<p> <a href="#heading" title="heading">link</a></p>
```