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 option htmlparser2 #161

Merged
merged 9 commits into from
Jun 4, 2020
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ parse(

The `replace` callback allows you to swap an element with another React element.

The first argument is an object with the same output as [htmlparser2](https://github.com/fb55/htmlparser2)'s [domhandler](https://github.com/fb55/domhandler#example):
The first argument is an object with the same output as [htmlparser2](https://github.com/fb55/htmlparser2/tree/v3.10.1)'s [domhandler](https://github.com/fb55/domhandler#example):

```js
parse('<br>', {
Expand Down Expand Up @@ -224,6 +224,34 @@ parse('<br>', {
});
```

### htmlparser2
Copy link
Owner Author

Choose a reason for hiding this comment

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


This library passes the following options to [htmlparser2](https://github.com/fb55/htmlparser2/tree/v3.10.1) on the server-side:

```js
{
decodeEntities: true,
lowerCaseAttributeNames: false
}
```

By passing your own options, the default library options will be **replaced** (not merged).

As a result, to enable `decodeEntities` and `xmlMode`, you need to do the following:

```js
parse('<p /><p />', {
htmlparser2: {
decodeEntities: true,
xmlMode: true
}
});
```

See [htmlparser2 options](https://github.com/fb55/htmlparser2/wiki/Parser-options).

> **Warning**: By overriding htmlparser2 options, there's a chance of breaking universal rendering. Do this at your own risk.

## FAQ

#### Is this library XSS safe?
Expand Down
17 changes: 10 additions & 7 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
// TypeScript Version: 3.3

import { DomElement } from 'domhandler';
import { DomElement, ParserOptions } from 'htmlparser2';
import domToReact from './lib/dom-to-react';
import htmlToDOM from 'html-dom-parser';

export interface HTMLReactParserOptions {
replace?: (
domNode: DomElement
) => JSX.Element | object | void | undefined | null | false;
htmlparser2?: ParserOptions;

library?: {
cloneElement: (
element: JSX.Element,
Expand All @@ -18,20 +17,24 @@ export interface HTMLReactParserOptions {
isValidElement: (element: any) => boolean;
[key: string]: any;
};

replace?: (
domNode: DomElement
) => JSX.Element | object | void | undefined | null | false;
}

/**
* Converts HTML string to JSX element(s).
*
* @param html - HTML string to parse to JSX element(s).
* @param html - HTML string.
* @param options - Parser options.
* @return - JSX element(s).
* @return - JSX element(s), empty array, or string.
*/
declare function HTMLReactParser(
html: string,
options?: HTMLReactParserOptions
): ReturnType<typeof domToReact>;

export { DomElement, domToReact, htmlToDOM };
export { DomElement, ParserOptions, domToReact, htmlToDOM };

export default HTMLReactParser;
16 changes: 11 additions & 5 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ var domParserOptions = { decodeEntities: true, lowerCaseAttributeNames: false };
/**
* Converts HTML string to React elements.
*
* @param {String} html - The HTML string to parse to React.
* @param {Object} [options] - The parser options.
* @param {Function} [options.replace] - The replace method.
* @return {JSX.Element|JSX.Element[]|String} - Returns React element(s), string, or empty array.
* @param {String} html - HTML string.
* @param {Object} [options] - Parser options.
* @param {Object} [options.htmlparser2] - htmlparser2 options.
* @param {Object} [options.library] - Library for React, Preact, etc.
* @param {Function} [options.replace] - Replace method.
* @return {JSX.Element|JSX.Element[]|String} - React element(s), empty array, or string.
*/
function HTMLReactParser(html, options) {
if (typeof html !== 'string') {
Expand All @@ -19,7 +21,11 @@ function HTMLReactParser(html, options) {
if (html === '') {
return [];
}
return domToReact(htmlToDOM(html, domParserOptions), options);
options = options || {};
return domToReact(
htmlToDOM(html, options.htmlparser2 || domParserOptions),
options
);
}

HTMLReactParser.domToReact = domToReact;
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"dom"
],
"dependencies": {
"@types/domhandler": "2.4.1",
"@types/htmlparser2": "3.10.1",
"html-dom-parser": "0.3.0",
"react-property": "1.0.1",
"style-to-object": "0.3.0"
Expand Down
25 changes: 25 additions & 0 deletions test/html-to-react.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,5 +127,30 @@ describe('HTML to React', () => {
);
});
});

describe('library', () => {
it('converts with Preact instead of React', () => {
const Preact = require('preact');
const html = data.html.single;
const options = { library: Preact };
const preactElement = parse(html, options);
assert.deepEqual(preactElement, Preact.createElement('p', {}, 'foo'));
});
});

describe('htmlparser2', () => {
it('parses XHTML with xmlMode enabled', () => {
// using self-closing syntax (`/>`) for non-void elements is invalid
// which causes elements to nest instead of being rendered correctly
// enabling htmlparser2 option xmlMode resolves this issue
const html = '<ul><li/><li/></ul>';
const options = { htmlparser2: { xmlMode: true } };
const reactElements = parse(html, options);
assert.strictEqual(
render(reactElements),
'<ul><li></li><li></li></ul>'
);
});
});
});
});
12 changes: 12 additions & 0 deletions test/types/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@ parse('<hr>', {
}
});

// $ExpectType Element | Element[]
parse('<p/><p/>', {
htmlparser2: {
xmlMode: true,
decodeEntities: true,
lowerCaseTags: false,
lowerCaseAttributeNames: false,
recognizeCDATA: true,
recognizeSelfClosing: true
}
});

// $ExpectType DomElement[]
const domNodes = htmlToDOM('<div>text</div>');

Expand Down