Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add preprocess and postprocess hooks #2730

Merged
merged 13 commits into from
Mar 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 78 additions & 1 deletion docs/USING_PRO.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ smartypants('"this ... string"')

<h2 id="walk-tokens">Walk Tokens : <code>walkTokens</code></h2>

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. When [`async`](#async) mode is enabled, the return value is awaited. Otherwise the return value is ignored.
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. When [`async`](#async) mode is enabled, the return value is awaited. Otherwise the return value 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*.

Expand Down Expand Up @@ -293,6 +293,83 @@ console.log(marked.parse('# heading 2\n\n## heading 3'));

***

<h2 id="hooks">Hooks : <code>hooks</code></h2>

Hooks are methods that hook into some part of marked. The following hooks are available:

| signature | description |
|-----------|-------------|
| `preprocess(markdown: string): string` | Process markdown before sending it to marked. |
| `postprocess(html: string): string` | Process html after marked has finished parsing. |

`marked.use()` can be called multiple times with different `hooks` functions. Each function will be called in order, starting with the function that was assigned *last*.

**Example:** Set options based on [front-matter](https://www.npmjs.com/package/front-matter)

```js
import { marked } from 'marked';
import fm from 'front-matter';

// Override function
const hooks = {
preprocess(markdown) {
const { attributes, body } = fm(markdown);
for (const prop in attributes) {
if (prop in this.options) {
this.options[prop] = attributes[prop];
}
}
return body;
}
};

marked.use({ hooks });

// Run marked
console.log(marked.parse(`
---
headerIds: false
---

## test
`.trim()));
```

**Output:**

```html
<h2>test</h2>
```

**Example:** Sanitize HTML with [isomorphic-dompurify](https://www.npmjs.com/package/isomorphic-dompurify)

```js
import { marked } from 'marked';
import DOMPurify from 'isomorphic-dompurify';

// Override function
const hooks = {
postprocess(html) {
return DOMPurify.sanitize(html);
}
};

marked.use({ hooks });

// Run marked
console.log(marked.parse(`
<img src=x onerror=alert(1)//>
`));
```

**Output:**

```html
<img src="x">
```

***

<h2 id="extensions">Custom Extensions : <code>extensions</code></h2>

You may supply an `extensions` array to the `options` object. This array can contain any number of `extension` objects, using the following properties:
Expand Down
1 change: 1 addition & 0 deletions docs/_document.html
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ <h1>Marked Documentation</h1>
<li><a href="/using_pro#renderer">Renderer</a></li>
<li><a href="/using_pro#tokenizer">Tokenizer</a></li>
<li><a href="/using_pro#walk-tokens">Walk Tokens</a></li>
<li><a href="/using_pro#hooks">Hooks</a></li>
<li><a href="/using_pro#extensions">Custom Extensions</a></li>
<li><a href="/using_pro#async">Async Marked</a></li>
<li><a href="/using_pro#lexer">Lexer</a></li>
Expand Down
26 changes: 26 additions & 0 deletions src/Hooks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { defaults } from './defaults.js';

export class Hooks {
constructor(options) {
this.options = options || defaults;
}

static passThroughHooks = new Set([
'preprocess',
'postprocess'
]);
Comment on lines +8 to +11
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently both preprocess and postprocess are "passthrough" hooks, which means the return value of one function gets passed to the next hook.

In the future I could see us adding some hooks that would not pass the return to the next hook. For example the way we call renderer or tokenizer methods is not pass through. The first tokenizer is called and if it returns something other than false we don't call the next tokenizer of the same name at all.


/**
* Process markdown before marked
*/
preprocess(markdown) {
return markdown;
}

/**
* Process HTML after marked is finished
*/
postprocess(html) {
return html;
}
}
1 change: 1 addition & 0 deletions src/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export function getDefaults() {
headerIds: true,
headerPrefix: '',
highlight: null,
hooks: null,
langPrefix: 'language-',
mangle: true,
pedantic: false,
Expand Down
17 changes: 0 additions & 17 deletions src/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,23 +142,6 @@ export function resolveUrl(base, href) {

export const noopTest = { exec: function noopTest() {} };

export function merge(obj) {
let i = 1,
target,
key;

for (; i < arguments.length; i++) {
target = arguments[i];
for (key in target) {
if (Object.prototype.hasOwnProperty.call(target, key)) {
obj[key] = target[key];
}
}
}

return obj;
}

export function splitCells(tableRow, count) {
// ensure that every cell-delimiting pipe has a space
// before it to distinguish it from an escaped pipe
Expand Down
Loading