diff --git a/README.md b/README.md index dd9a0d43..a242442d 100644 --- a/README.md +++ b/README.md @@ -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('
', { @@ -224,6 +224,34 @@ parse('
', { }); ``` +### htmlparser2 + +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('

', { + 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? diff --git a/index.d.ts b/index.d.ts index 4bcc997d..28b40dc3 100644 --- a/index.d.ts +++ b/index.d.ts @@ -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, @@ -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; -export { DomElement, domToReact, htmlToDOM }; +export { DomElement, ParserOptions, domToReact, htmlToDOM }; export default HTMLReactParser; diff --git a/index.js b/index.js index b6fb44b9..a4e859ca 100644 --- a/index.js +++ b/index.js @@ -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') { @@ -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; diff --git a/package.json b/package.json index aa28ec36..2b6a1d3e 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/test/html-to-react.js b/test/html-to-react.js index 31e59410..15bf7085 100644 --- a/test/html-to-react.js +++ b/test/html-to-react.js @@ -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 = '

'; + const options = { htmlparser2: { xmlMode: true } }; + const reactElements = parse(html, options); + assert.strictEqual( + render(reactElements), + '' + ); + }); + }); }); }); diff --git a/test/types/index.test.tsx b/test/types/index.test.tsx index 30314694..9fbef9b4 100644 --- a/test/types/index.test.tsx +++ b/test/types/index.test.tsx @@ -60,6 +60,18 @@ parse('
', { } }); +// $ExpectType Element | Element[] +parse('

', { + htmlparser2: { + xmlMode: true, + decodeEntities: true, + lowerCaseTags: false, + lowerCaseAttributeNames: false, + recognizeCDATA: true, + recognizeSelfClosing: true + } +}); + // $ExpectType DomElement[] const domNodes = htmlToDOM('

text
');