Skip to content

Commit 93cccf1

Browse files
authored
Optionally disable HTML parsing (#278)
* Add disableParsingRawHTML option to disable parsing raw html * Update README with usage of disableParsingRawHTML * Tests for inline JSX and overrides * Bump the allowed size by a few bytes to accommodate this change
1 parent 3481d93 commit 93cccf1

File tree

4 files changed

+158
-59
lines changed

4 files changed

+158
-59
lines changed

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ The most lightweight, customizable React markdown component.
1717
- [options.createElement - Custom React.createElement behavior](#optionscreateelement---custom-reactcreateelement-behavior)
1818
- [options.slugify](#optionsslugify)
1919
- [options.namedCodesToUnicode](#optionsnamedcodestounicode)
20+
- [options.disableParsingRawHTML](#optionsdisableparsingrawhtml)
2021
- [Getting the smallest possible bundle size](#getting-the-smallest-possible-bundle-size)
2122
- [Usage with Preact](#usage-with-preact)
2223
- [Gotchas](#gotchas)
@@ -390,6 +391,24 @@ compiler('This text is ≤ than this text.', namedCodesToUnicode: {
390391
<p>This text is ≤ than this text.</p>
391392
```
392393

394+
#### options.disableParsingRawHTML
395+
396+
By default, raw HTML is parsed to JSX. This behavior can be disabled with this option.
397+
398+
```jsx
399+
<Markdown options={{ disableParsingRawHTML: true }}>
400+
This text has <span>html</span> in it but it won't be rendered
401+
</Markdown>;
402+
403+
// or
404+
405+
compiler('This text has <span>html</span> in it but it won't be rendered', { disableParsingRawHTML: true });
406+
407+
// renders:
408+
409+
<span>This text has &lt;span&gt;html&lt;/span&gt; in it but it won't be rendered</span>
410+
```
411+
393412
### Getting the smallest possible bundle size
394413
395414
Many development conveniences are placed behind `process.env.NODE_ENV !== "production"` conditionals. When bundling your app, it's a good idea to replace these code snippets such that a minifier (like uglify) can sweep them away and leave a smaller overall bundle.

index.compiler.spec.js

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2820,6 +2820,46 @@ fun main() {
28202820
</p>
28212821
</div>
28222822
2823+
`);
2824+
});
2825+
2826+
it('should not render html if disableParsingRawHTML is true', () => {
2827+
render(
2828+
compiler(
2829+
'Text with <span>html</span> inside',
2830+
{
2831+
disableParsingRawHTML: true
2832+
}
2833+
)
2834+
);
2835+
expect(root.innerHTML).toMatchInlineSnapshot(`
2836+
2837+
<span data-reactroot>
2838+
Text with &lt;span&gt;html&lt;/span&gt; inside
2839+
</span>
2840+
2841+
`);
2842+
});
2843+
2844+
it('should render html if disableParsingRawHTML is false', () => {
2845+
render(
2846+
compiler(
2847+
'Text with <span>html</span> inside',
2848+
{
2849+
disableParsingRawHTML: false
2850+
}
2851+
)
2852+
);
2853+
expect(root.innerHTML).toMatchInlineSnapshot(`
2854+
2855+
<span data-reactroot>
2856+
Text with
2857+
<span>
2858+
html
2859+
</span>
2860+
inside
2861+
</span>
2862+
28232863
`);
28242864
});
28252865
});
@@ -3300,6 +3340,44 @@ describe('overrides', () => {
33003340
value="on"
33013341
>
33023342
3343+
`);
3344+
});
3345+
3346+
it('should substitute the appropriate JSX tag if given a component and disableParsingRawHTML is true', () => {
3347+
const FakeParagraph = ({ children }) => <p className="foo">{children}</p>;
3348+
3349+
render(
3350+
compiler('Hello.\n\n', {
3351+
overrides: { p: { component: FakeParagraph } },
3352+
options: { disableParsingRawHTML: true }
3353+
})
3354+
);
3355+
3356+
expect(root.children[0].className).toBe('foo');
3357+
expect(root.children[0].textContent).toBe('Hello.');
3358+
});
3359+
3360+
it('should substitute the appropriate JSX tag inline if given a component and disableParsingRawHTML is true', () => {
3361+
const FakeSpan = ({ children }) => <span className="foo">{children}</span>;
3362+
3363+
render(
3364+
compiler('Hello.\n\n<FakeSpan>I am a fake span</FakeSpan>', {
3365+
overrides: { FakeSpan },
3366+
options: { disableParsingRawHTML: true }
3367+
})
3368+
);
3369+
3370+
expect(root.innerHTML).toMatchInlineSnapshot(`
3371+
3372+
<div data-reactroot>
3373+
<p>
3374+
Hello.
3375+
</p>
3376+
<span class="foo">
3377+
I am a fake span
3378+
</span>
3379+
</div>
3380+
33033381
`);
33043382
});
33053383
});

index.js

Lines changed: 60 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,47 +1030,6 @@ export function compiler(markdown, options) {
10301030
},
10311031
},
10321032

1033-
htmlBlock: {
1034-
/**
1035-
* find the first matching end tag and process the interior
1036-
*/
1037-
match: anyScopeRegex(HTML_BLOCK_ELEMENT_R),
1038-
order: PARSE_PRIORITY_HIGH,
1039-
parse(capture, parse, state) {
1040-
const [, whitespace] = capture[3].match(HTML_LEFT_TRIM_AMOUNT_R);
1041-
const trimmer = new RegExp(`^${whitespace}`, 'gm');
1042-
const trimmed = capture[3].replace(trimmer, '');
1043-
1044-
const parseFunc = containsBlockSyntax(trimmed)
1045-
? parseBlock
1046-
: parseInline;
1047-
1048-
const tagName = capture[1].toLowerCase();
1049-
const noInnerParse =
1050-
DO_NOT_PROCESS_HTML_ELEMENTS.indexOf(tagName) !== -1;
1051-
1052-
return {
1053-
attrs: attrStringToMap(capture[2]),
1054-
/**
1055-
* if another html block is detected within, parse as block,
1056-
* otherwise parse as inline to pick up any further markdown
1057-
*/
1058-
content: noInnerParse ? capture[3] : parseFunc(parse, trimmed, state),
1059-
1060-
noInnerParse,
1061-
1062-
tag: noInnerParse ? tagName : capture[1]
1063-
};
1064-
},
1065-
react(node, output, state) {
1066-
return (
1067-
<node.tag key={state.key} {...node.attrs}>
1068-
{node.noInnerParse ? node.content : output(node.content, state)}
1069-
</node.tag>
1070-
);
1071-
},
1072-
},
1073-
10741033
htmlComment: {
10751034
match: anyScopeRegex(HTML_COMMENT_R),
10761035
order: PARSE_PRIORITY_HIGH,
@@ -1080,23 +1039,6 @@ export function compiler(markdown, options) {
10801039
react: renderNothing,
10811040
},
10821041

1083-
htmlSelfClosing: {
1084-
/**
1085-
* find the first matching end tag and process the interior
1086-
*/
1087-
match: anyScopeRegex(HTML_SELF_CLOSING_ELEMENT_R),
1088-
order: PARSE_PRIORITY_HIGH,
1089-
parse(capture /*, parse, state*/) {
1090-
return {
1091-
attrs: attrStringToMap(capture[2] || ''),
1092-
tag: capture[1],
1093-
};
1094-
},
1095-
react(node, output, state) {
1096-
return <node.tag {...node.attrs} key={state.key} />;
1097-
},
1098-
},
1099-
11001042
image: {
11011043
match: simpleInlineRegex(IMAGE_R),
11021044
order: PARSE_PRIORITY_HIGH,
@@ -1553,6 +1495,66 @@ export function compiler(markdown, options) {
15531495
// };
15541496
// });
15551497

1498+
if (options.disableParsingRawHTML !== true) {
1499+
rules.htmlBlock = {
1500+
/**
1501+
* find the first matching end tag and process the interior
1502+
*/
1503+
match: anyScopeRegex(HTML_BLOCK_ELEMENT_R),
1504+
order: PARSE_PRIORITY_HIGH,
1505+
parse(capture, parse, state) {
1506+
const [, whitespace] = capture[3].match(HTML_LEFT_TRIM_AMOUNT_R);
1507+
const trimmer = new RegExp(`^${whitespace}`, 'gm');
1508+
const trimmed = capture[3].replace(trimmer, '');
1509+
1510+
const parseFunc = containsBlockSyntax(trimmed)
1511+
? parseBlock
1512+
: parseInline;
1513+
1514+
const tagName = capture[1].toLowerCase();
1515+
const noInnerParse =
1516+
DO_NOT_PROCESS_HTML_ELEMENTS.indexOf(tagName) !== -1;
1517+
1518+
return {
1519+
attrs: attrStringToMap(capture[2]),
1520+
/**
1521+
* if another html block is detected within, parse as block,
1522+
* otherwise parse as inline to pick up any further markdown
1523+
*/
1524+
content: noInnerParse ? capture[3] : parseFunc(parse, trimmed, state),
1525+
1526+
noInnerParse,
1527+
1528+
tag: noInnerParse ? tagName : capture[1]
1529+
};
1530+
},
1531+
react(node, output, state) {
1532+
return (
1533+
<node.tag key={state.key} {...node.attrs}>
1534+
{node.noInnerParse ? node.content : output(node.content, state)}
1535+
</node.tag>
1536+
);
1537+
},
1538+
}
1539+
1540+
rules.htmlSelfClosing = {
1541+
/**
1542+
* find the first matching end tag and process the interior
1543+
*/
1544+
match: anyScopeRegex(HTML_SELF_CLOSING_ELEMENT_R),
1545+
order: PARSE_PRIORITY_HIGH,
1546+
parse(capture /*, parse, state*/) {
1547+
return {
1548+
attrs: attrStringToMap(capture[2] || ''),
1549+
tag: capture[1],
1550+
};
1551+
},
1552+
react(node, output, state) {
1553+
return <node.tag {...node.attrs} key={state.key} />;
1554+
},
1555+
};
1556+
}
1557+
15561558
const parser = parserFor(rules);
15571559
const emitter = reactFor(ruleOutput(rules));
15581560

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@
9090
"size-limit": [
9191
{
9292
"path": "dist/cjs.js",
93-
"limit": "5.25 kB"
93+
"limit": "5.28 kB"
9494
}
9595
],
9696
"jest": {

0 commit comments

Comments
 (0)