From 259eca1644682d3a37a2dd6c8cd31ed87aa6a942 Mon Sep 17 00:00:00 2001 From: Hans Otto Wirtz Date: Thu, 20 Jul 2023 18:43:46 +0200 Subject: [PATCH] feat: add transform option --- README.md | 16 ++++++++++++++++ index.d.ts | 6 ++++++ lib/dom-to-react.js | 8 +++++--- lib/utilities.js | 7 ++++++- test/dom-to-react.test.js | 37 +++++++++++++++++++++++++++++++++++++ 5 files changed, 70 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5a6a3a59..726fe8ee 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ parse('

Hello, World!

'); // React.createElement('p', {}, 'Hello, World!') - [replace element and children](#replace-element-and-children) - [replace element attributes](#replace-element-attributes) - [replace and remove element](#replace-and-remove-element) + - [transform](#transform) - [library](#library) - [htmlparser2](#htmlparser2) - [trim](#trim) @@ -303,6 +304,21 @@ HTML output:

``` +### transform + +The `transform` option allows you to transform each element individually after it's parsed. + +The `transform` callback's first argument is the React element: + +```jsx +parse('
', { + transform: (reactNode, domNode, index) => { + // this will wrap every element in a div + return
{reactNode}
; + } +}); +``` + ### library The `library` option specifies the UI library. The default library is **React**. diff --git a/index.d.ts b/index.d.ts index bef168d2..de8b0efc 100644 --- a/index.d.ts +++ b/index.d.ts @@ -38,6 +38,12 @@ export interface HTMLReactParserOptions { domNode: DOMNode ) => JSX.Element | object | void | undefined | null | false; + transform?: ( + reactNode: JSX.Element | string, + domNode: DOMNode, + index: number + ) => JSX.Element | string | null; + trim?: boolean; } diff --git a/lib/dom-to-react.js b/lib/dom-to-react.js index 4f46d65c..fc7a4a6a 100644 --- a/lib/dom-to-react.js +++ b/lib/dom-to-react.js @@ -11,6 +11,7 @@ var canTextBeChildOfNode = utilities.canTextBeChildOfNode; * @param {DomElement[]} nodes - DOM nodes. * @param {object} [options={}] - Options. * @param {Function} [options.replace] - Replacer. + * @param {Function} [options.transform] - Transform. * @param {object} [options.library] - Library (React, Preact, etc.). * @returns - String or JSX element(s). */ @@ -26,6 +27,7 @@ function domToReact(nodes, options) { var node; var isWhitespace; var hasReplace = typeof options.replace === 'function'; + var transform = options.transform || utilities.returnFirstArg; var replaceElement; var props; var children; @@ -46,7 +48,7 @@ function domToReact(nodes, options) { key: replaceElement.key || i }); } - result.push(replaceElement); + result.push(transform(replaceElement, node, i)); continue; } } @@ -68,7 +70,7 @@ function domToReact(nodes, options) { // We have a text node that's not whitespace and it can be nested // in its parent so add it to the results - result.push(node.data); + result.push(transform(node.data, node, i)); continue; } @@ -115,7 +117,7 @@ function domToReact(nodes, options) { props.key = i; } - result.push(createElement(node.name, props, children)); + result.push(transform(createElement(node.name, props, children), node, i)); } return result.length === 1 ? result[0] : result; diff --git a/lib/utilities.js b/lib/utilities.js index efd24e0c..0321f949 100644 --- a/lib/utilities.js +++ b/lib/utilities.js @@ -120,11 +120,16 @@ function canTextBeChildOfNode(node) { return !elementsWithNoTextChildren.has(node.name); } +function returnFirstArg(arg) { + return arg; +} + module.exports = { PRESERVE_CUSTOM_ATTRIBUTES: PRESERVE_CUSTOM_ATTRIBUTES, invertObject: invertObject, isCustomComponent: isCustomComponent, setStyleProp: setStyleProp, canTextBeChildOfNode: canTextBeChildOfNode, - elementsWithNoTextChildren: elementsWithNoTextChildren + elementsWithNoTextChildren: elementsWithNoTextChildren, + returnFirstArg: returnFirstArg }; diff --git a/test/dom-to-react.test.js b/test/dom-to-react.test.js index 4409beaf..0aea3ac5 100644 --- a/test/dom-to-react.test.js +++ b/test/dom-to-react.test.js @@ -206,6 +206,43 @@ describe('domToReact replace option', () => { }); }); +describe('domToReact transform option', () => { + it('can wrap all elements', () => { + const options = { + transform: (reactNode, domNode, i) => { + return React.createElement('div', { key: i }, reactNode); + } + }; + + const reactElement = domToReact(htmlToDOM(html.list), options); + expect(reactElement.key).toBe('0'); + expect(reactElement.props.children.props.children[0].key).toBe('0'); + expect(reactElement.props.children.props.children[1].key).toBe('1'); + expect(reactElement).toMatchInlineSnapshot(` +
+
    +
    +
  1. +
    + One +
    +
  2. +
    +
    +
  3. +
    + Two +
    +
  4. +
    +
+
+ `); + }); +}); + describe('domToReact', () => { describe('when React >=16', () => { it('preserves unknown attributes', () => {