html-react-parser
Version:
HTML to React parser.
131 lines (112 loc) • 3.6 kB
JavaScript
var React = require('react');
var attributesToProps = require('./attributes-to-props');
var utilities = require('./utilities');
var setStyleProp = utilities.setStyleProp;
/**
* Converts DOM nodes to JSX element(s).
*
* @param {DomElement[]} nodes - DOM nodes.
* @param {object} [options={}] - Options.
* @param {Function} [options.replace] - Replacer.
* @param {object} [options.library] - Library (React/Preact/etc.).
* @return {string|JSX.Element|JSX.Element[]}
*/
function domToReact(nodes, options) {
options = options || {};
var library = options.library || React;
var cloneElement = library.cloneElement;
var createElement = library.createElement;
var isValidElement = library.isValidElement;
var result = [];
var node;
var hasReplace = typeof options.replace === 'function';
var replaceElement;
var props;
var children;
var data;
var trim = options.trim;
for (var i = 0, len = nodes.length; i < len; i++) {
node = nodes[i];
// replace with custom React element (if present)
if (hasReplace) {
replaceElement = options.replace(node);
if (isValidElement(replaceElement)) {
// set "key" prop for sibling elements
// https://fb.me/react-warning-keys
if (len > 1) {
replaceElement = cloneElement(replaceElement, {
key: replaceElement.key || i
});
}
result.push(replaceElement);
continue;
}
}
if (node.type === 'text') {
// if trim option is enabled, skip whitespace text nodes
if (trim) {
data = node.data.trim();
if (data) {
result.push(node.data);
}
} else {
result.push(node.data);
}
continue;
}
props = node.attribs;
if (skipAttributesToProps(node)) {
setStyleProp(props.style, props);
} else if (props) {
props = attributesToProps(props);
}
children = null;
switch (node.type) {
case 'script':
case 'style':
// prevent text in <script> or <style> from being escaped
// https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
if (node.children[0]) {
props.dangerouslySetInnerHTML = {
__html: node.children[0].data
};
}
break;
case 'tag':
// setting textarea value in children is an antipattern in React
// https://reactjs.org/docs/forms.html#the-textarea-tag
if (node.name === 'textarea' && node.children[0]) {
props.defaultValue = node.children[0].data;
} else if (node.children && node.children.length) {
// continue recursion of creating React elements (if applicable)
children = domToReact(node.children, options);
}
break;
// skip all other cases (e.g., comment)
default:
continue;
}
// set "key" prop for sibling elements
// https://fb.me/react-warning-keys
if (len > 1) {
props.key = i;
}
result.push(createElement(node.name, props, children));
}
return result.length === 1 ? result[0] : result;
}
/**
* Determines whether DOM element attributes should be transformed to props.
* Web Components should not have their attributes transformed except for `style`.
*
* @param {DomElement} node
* @return {boolean}
*/
function skipAttributesToProps(node) {
return (
utilities.PRESERVE_CUSTOM_ATTRIBUTES &&
node.type === 'tag' &&
utilities.isCustomComponent(node.name, node.attribs)
);
}
module.exports = domToReact;