UNPKG

html-react-parser

Version:
1,692 lines (1,525 loc) 43.3 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('react')) : typeof define === 'function' && define.amd ? define(['react'], factory) : (global = global || self, global.HTMLReactParser = factory(global.React)); }(this, (function (react) { 'use strict'; react = react && Object.prototype.hasOwnProperty.call(react, 'default') ? react['default'] : react; var HTMLDOMPropertyConfig = { Properties: { autoFocus: 4, accept: 0, acceptCharset: 0, accessKey: 0, action: 0, allowFullScreen: 4, allowTransparency: 0, alt: 0, as: 0, async: 4, autoComplete: 0, autoPlay: 4, capture: 4, cellPadding: 0, cellSpacing: 0, charSet: 0, challenge: 0, checked: 5, cite: 0, classID: 0, className: 0, cols: 24, colSpan: 0, content: 0, contentEditable: 0, contextMenu: 0, controls: 4, controlsList: 0, coords: 0, crossOrigin: 0, data: 0, dateTime: 0, default: 4, defer: 4, dir: 0, disabled: 4, download: 32, draggable: 0, encType: 0, form: 0, formAction: 0, formEncType: 0, formMethod: 0, formNoValidate: 4, formTarget: 0, frameBorder: 0, headers: 0, height: 0, hidden: 4, high: 0, href: 0, hrefLang: 0, htmlFor: 0, httpEquiv: 0, icon: 0, id: 0, inputMode: 0, integrity: 0, is: 0, keyParams: 0, keyType: 0, kind: 0, label: 0, lang: 0, list: 0, loop: 4, low: 0, manifest: 0, marginHeight: 0, marginWidth: 0, max: 0, maxLength: 0, media: 0, mediaGroup: 0, method: 0, min: 0, minLength: 0, multiple: 5, muted: 5, name: 0, nonce: 0, noValidate: 4, open: 4, optimum: 0, pattern: 0, placeholder: 0, playsInline: 4, poster: 0, preload: 0, profile: 0, radioGroup: 0, readOnly: 4, referrerPolicy: 0, rel: 0, required: 4, reversed: 4, role: 0, rows: 24, rowSpan: 8, sandbox: 0, scope: 0, scoped: 4, scrolling: 0, seamless: 4, selected: 5, shape: 0, size: 24, sizes: 0, span: 24, spellCheck: 0, src: 0, srcDoc: 0, srcLang: 0, srcSet: 0, start: 8, step: 0, style: 0, summary: 0, tabIndex: 0, target: 0, title: 0, type: 0, useMap: 0, value: 0, width: 0, wmode: 0, wrap: 0, about: 0, datatype: 0, inlist: 0, prefix: 0, property: 0, resource: 0, typeof: 0, vocab: 0, autoCapitalize: 0, autoCorrect: 0, autoSave: 0, color: 0, itemProp: 0, itemScope: 4, itemType: 0, itemID: 0, itemRef: 0, results: 0, security: 0, unselectable: 0 }, DOMAttributeNames: { acceptCharset: 'accept-charset', className: 'class', htmlFor: 'for', httpEquiv: 'http-equiv' } }; var SVGDOMPropertyConfig = { Properties: { accentHeight: 0, accumulate: 0, additive: 0, alignmentBaseline: 0, allowReorder: 0, alphabetic: 0, amplitude: 0, arabicForm: 0, ascent: 0, attributeName: 0, attributeType: 0, autoReverse: 0, azimuth: 0, baseFrequency: 0, baseProfile: 0, baselineShift: 0, bbox: 0, begin: 0, bias: 0, by: 0, calcMode: 0, capHeight: 0, clip: 0, clipPath: 0, clipRule: 0, clipPathUnits: 0, colorInterpolation: 0, colorInterpolationFilters: 0, colorProfile: 0, colorRendering: 0, contentScriptType: 0, contentStyleType: 0, cursor: 0, cx: 0, cy: 0, d: 0, decelerate: 0, descent: 0, diffuseConstant: 0, direction: 0, display: 0, divisor: 0, dominantBaseline: 0, dur: 0, dx: 0, dy: 0, edgeMode: 0, elevation: 0, enableBackground: 0, end: 0, exponent: 0, externalResourcesRequired: 0, fill: 0, fillOpacity: 0, fillRule: 0, filter: 0, filterRes: 0, filterUnits: 0, floodColor: 0, floodOpacity: 0, focusable: 0, fontFamily: 0, fontSize: 0, fontSizeAdjust: 0, fontStretch: 0, fontStyle: 0, fontVariant: 0, fontWeight: 0, format: 0, from: 0, fx: 0, fy: 0, g1: 0, g2: 0, glyphName: 0, glyphOrientationHorizontal: 0, glyphOrientationVertical: 0, glyphRef: 0, gradientTransform: 0, gradientUnits: 0, hanging: 0, horizAdvX: 0, horizOriginX: 0, ideographic: 0, imageRendering: 0, in: 0, in2: 0, intercept: 0, k: 0, k1: 0, k2: 0, k3: 0, k4: 0, kernelMatrix: 0, kernelUnitLength: 0, kerning: 0, keyPoints: 0, keySplines: 0, keyTimes: 0, lengthAdjust: 0, letterSpacing: 0, lightingColor: 0, limitingConeAngle: 0, local: 0, markerEnd: 0, markerMid: 0, markerStart: 0, markerHeight: 0, markerUnits: 0, markerWidth: 0, mask: 0, maskContentUnits: 0, maskUnits: 0, mathematical: 0, mode: 0, numOctaves: 0, offset: 0, opacity: 0, operator: 0, order: 0, orient: 0, orientation: 0, origin: 0, overflow: 0, overlinePosition: 0, overlineThickness: 0, paintOrder: 0, panose1: 0, pathLength: 0, patternContentUnits: 0, patternTransform: 0, patternUnits: 0, pointerEvents: 0, points: 0, pointsAtX: 0, pointsAtY: 0, pointsAtZ: 0, preserveAlpha: 0, preserveAspectRatio: 0, primitiveUnits: 0, r: 0, radius: 0, refX: 0, refY: 0, renderingIntent: 0, repeatCount: 0, repeatDur: 0, requiredExtensions: 0, requiredFeatures: 0, restart: 0, result: 0, rotate: 0, rx: 0, ry: 0, scale: 0, seed: 0, shapeRendering: 0, slope: 0, spacing: 0, specularConstant: 0, specularExponent: 0, speed: 0, spreadMethod: 0, startOffset: 0, stdDeviation: 0, stemh: 0, stemv: 0, stitchTiles: 0, stopColor: 0, stopOpacity: 0, strikethroughPosition: 0, strikethroughThickness: 0, string: 0, stroke: 0, strokeDasharray: 0, strokeDashoffset: 0, strokeLinecap: 0, strokeLinejoin: 0, strokeMiterlimit: 0, strokeOpacity: 0, strokeWidth: 0, surfaceScale: 0, systemLanguage: 0, tableValues: 0, targetX: 0, targetY: 0, textAnchor: 0, textDecoration: 0, textRendering: 0, textLength: 0, to: 0, transform: 0, u1: 0, u2: 0, underlinePosition: 0, underlineThickness: 0, unicode: 0, unicodeBidi: 0, unicodeRange: 0, unitsPerEm: 0, vAlphabetic: 0, vHanging: 0, vIdeographic: 0, vMathematical: 0, values: 0, vectorEffect: 0, version: 0, vertAdvY: 0, vertOriginX: 0, vertOriginY: 0, viewBox: 0, viewTarget: 0, visibility: 0, widths: 0, wordSpacing: 0, writingMode: 0, x: 0, xHeight: 0, x1: 0, x2: 0, xChannelSelector: 0, xlinkActuate: 0, xlinkArcrole: 0, xlinkHref: 0, xlinkRole: 0, xlinkShow: 0, xlinkTitle: 0, xlinkType: 0, xmlBase: 0, xmlns: 0, xmlnsXlink: 0, xmlLang: 0, xmlSpace: 0, y: 0, y1: 0, y2: 0, yChannelSelector: 0, z: 0, zoomAndPan: 0 }, DOMAttributeNames: { accentHeight: 'accent-height', alignmentBaseline: 'alignment-baseline', arabicForm: 'arabic-form', baselineShift: 'baseline-shift', capHeight: 'cap-height', clipPath: 'clip-path', clipRule: 'clip-rule', colorInterpolation: 'color-interpolation', colorInterpolationFilters: 'color-interpolation-filters', colorProfile: 'color-profile', colorRendering: 'color-rendering', dominantBaseline: 'dominant-baseline', enableBackground: 'enable-background', fillOpacity: 'fill-opacity', fillRule: 'fill-rule', floodColor: 'flood-color', floodOpacity: 'flood-opacity', fontFamily: 'font-family', fontSize: 'font-size', fontSizeAdjust: 'font-size-adjust', fontStretch: 'font-stretch', fontStyle: 'font-style', fontVariant: 'font-variant', fontWeight: 'font-weight', glyphName: 'glyph-name', glyphOrientationHorizontal: 'glyph-orientation-horizontal', glyphOrientationVertical: 'glyph-orientation-vertical', horizAdvX: 'horiz-adv-x', horizOriginX: 'horiz-origin-x', imageRendering: 'image-rendering', letterSpacing: 'letter-spacing', lightingColor: 'lighting-color', markerEnd: 'marker-end', markerMid: 'marker-mid', markerStart: 'marker-start', overlinePosition: 'overline-position', overlineThickness: 'overline-thickness', paintOrder: 'paint-order', panose1: 'panose-1', pointerEvents: 'pointer-events', renderingIntent: 'rendering-intent', shapeRendering: 'shape-rendering', stopColor: 'stop-color', stopOpacity: 'stop-opacity', strikethroughPosition: 'strikethrough-position', strikethroughThickness: 'strikethrough-thickness', strokeDasharray: 'stroke-dasharray', strokeDashoffset: 'stroke-dashoffset', strokeLinecap: 'stroke-linecap', strokeLinejoin: 'stroke-linejoin', strokeMiterlimit: 'stroke-miterlimit', strokeOpacity: 'stroke-opacity', strokeWidth: 'stroke-width', textAnchor: 'text-anchor', textDecoration: 'text-decoration', textRendering: 'text-rendering', underlinePosition: 'underline-position', underlineThickness: 'underline-thickness', unicodeBidi: 'unicode-bidi', unicodeRange: 'unicode-range', unitsPerEm: 'units-per-em', vAlphabetic: 'v-alphabetic', vHanging: 'v-hanging', vIdeographic: 'v-ideographic', vMathematical: 'v-mathematical', vectorEffect: 'vector-effect', vertAdvY: 'vert-adv-y', vertOriginX: 'vert-origin-x', vertOriginY: 'vert-origin-y', wordSpacing: 'word-spacing', writingMode: 'writing-mode', xHeight: 'x-height', xlinkActuate: 'xlink:actuate', xlinkArcrole: 'xlink:arcrole', xlinkHref: 'xlink:href', xlinkRole: 'xlink:role', xlinkShow: 'xlink:show', xlinkTitle: 'xlink:title', xlinkType: 'xlink:type', xmlBase: 'xml:base', xmlnsXlink: 'xmlns:xlink', xmlLang: 'xml:lang', xmlSpace: 'xml:space' } }; var injection = { MUST_USE_PROPERTY: 1, HAS_BOOLEAN_VALUE: 4, HAS_NUMERIC_VALUE: 8, HAS_POSITIVE_NUMERIC_VALUE: 24, HAS_OVERLOADED_BOOLEAN_VALUE: 32 }; var MUST_USE_PROPERTY = injection.MUST_USE_PROPERTY; var HAS_BOOLEAN_VALUE = injection.HAS_BOOLEAN_VALUE; var HAS_NUMERIC_VALUE = injection.HAS_NUMERIC_VALUE; var HAS_POSITIVE_NUMERIC_VALUE = injection.HAS_POSITIVE_NUMERIC_VALUE; var HAS_OVERLOADED_BOOLEAN_VALUE = injection.HAS_OVERLOADED_BOOLEAN_VALUE; /** * @see https://github.com/facebook/react/blob/15-stable/src/renderers/dom/shared/DOMProperty.js#L14-L16 * * @param {Number} value * @param {Number} bitmask * @return {Boolean} */ function checkMask(value, bitmask) { return (value & bitmask) === bitmask; } /** * @see https://github.com/facebook/react/blob/15-stable/src/renderers/dom/shared/DOMProperty.js#L57 * * @param {Object} domPropertyConfig - HTMLDOMPropertyConfig or SVGDOMPropertyConfig * @param {Object} config - The object to be mutated * @param {Boolean} isSVG - Whether the injected config is HTML or SVG (it assumes the default is HTML) */ function injectDOMPropertyConfig(domPropertyConfig, config, isSVG) { var Properties = domPropertyConfig.Properties; var DOMAttributeNames = domPropertyConfig.DOMAttributeNames; var attributeName; var propertyName; var propConfig; for (propertyName in Properties) { attributeName = DOMAttributeNames[propertyName] || (isSVG ? propertyName : propertyName.toLowerCase()); propConfig = Properties[propertyName]; config[attributeName] = { attributeName: attributeName, propertyName: propertyName, mustUseProperty: checkMask(propConfig, MUST_USE_PROPERTY), hasBooleanValue: checkMask(propConfig, HAS_BOOLEAN_VALUE), hasNumericValue: checkMask(propConfig, HAS_NUMERIC_VALUE), hasPositiveNumericValue: checkMask( propConfig, HAS_POSITIVE_NUMERIC_VALUE ), hasOverloadedBooleanValue: checkMask( propConfig, HAS_OVERLOADED_BOOLEAN_VALUE ) }; } } /** * HTML properties config. * * @type {Object} */ var html = {}; injectDOMPropertyConfig(HTMLDOMPropertyConfig, html); /** * SVG properties config. * * @type {Object} */ var svg = {}; injectDOMPropertyConfig(SVGDOMPropertyConfig, svg, true); /** * HTML and SVG properties config. * * @type {Object} */ var properties = {}; injectDOMPropertyConfig(HTMLDOMPropertyConfig, properties); injectDOMPropertyConfig(SVGDOMPropertyConfig, properties, true); var ATTRIBUTE_NAME_START_CHAR = ':A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD'; var ATTRIBUTE_NAME_CHAR = ATTRIBUTE_NAME_START_CHAR + '\\-.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040'; var reactProperty = { html: html, svg: svg, properties: properties, /** * Checks whether a property name is a custom attribute. * * @see https://github.com/facebook/react/blob/15-stable/src/renderers/dom/shared/HTMLDOMPropertyConfig.js#L23-L25 * * @param {String} * @return {Boolean} */ isCustomAttribute: RegExp.prototype.test.bind( new RegExp('^(data|aria)-[' + ATTRIBUTE_NAME_CHAR + ']*$') ) }; // http://www.w3.org/TR/CSS21/grammar.html // https://github.com/visionmedia/css-parse/pull/49#issuecomment-30088027 var COMMENT_REGEX = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//g; var NEWLINE_REGEX = /\n/g; var WHITESPACE_REGEX = /^\s*/; // declaration var PROPERTY_REGEX = /^(\*?[-#/*\\\w]+(\[[0-9a-z_-]+\])?)\s*/; var COLON_REGEX = /^:\s*/; var VALUE_REGEX = /^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^)]*?\)|[^};])+)/; var SEMICOLON_REGEX = /^[;\s]*/; // https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim#Polyfill var TRIM_REGEX = /^\s+|\s+$/g; // strings var NEWLINE = '\n'; var FORWARD_SLASH = '/'; var ASTERISK = '*'; var EMPTY_STRING = ''; // types var TYPE_COMMENT = 'comment'; var TYPE_DECLARATION = 'declaration'; /** * @param {String} style * @param {Object} [options] * @return {Object[]} * @throws {TypeError} * @throws {Error} */ var inlineStyleParser = function(style, options) { if (typeof style !== 'string') { throw new TypeError('First argument must be a string'); } if (!style) return []; options = options || {}; /** * Positional. */ var lineno = 1; var column = 1; /** * Update lineno and column based on `str`. * * @param {String} str */ function updatePosition(str) { var lines = str.match(NEWLINE_REGEX); if (lines) lineno += lines.length; var i = str.lastIndexOf(NEWLINE); column = ~i ? str.length - i : column + str.length; } /** * Mark position and patch `node.position`. * * @return {Function} */ function position() { var start = { line: lineno, column: column }; return function(node) { node.position = new Position(start); whitespace(); return node; }; } /** * Store position information for a node. * * @constructor * @property {Object} start * @property {Object} end * @property {undefined|String} source */ function Position(start) { this.start = start; this.end = { line: lineno, column: column }; this.source = options.source; } /** * Non-enumerable source string. */ Position.prototype.content = style; /** * Error `msg`. * * @param {String} msg * @throws {Error} */ function error(msg) { var err = new Error( options.source + ':' + lineno + ':' + column + ': ' + msg ); err.reason = msg; err.filename = options.source; err.line = lineno; err.column = column; err.source = style; if (options.silent) ; else { throw err; } } /** * Match `re` and return captures. * * @param {RegExp} re * @return {undefined|Array} */ function match(re) { var m = re.exec(style); if (!m) return; var str = m[0]; updatePosition(str); style = style.slice(str.length); return m; } /** * Parse whitespace. */ function whitespace() { match(WHITESPACE_REGEX); } /** * Parse comments. * * @param {Object[]} [rules] * @return {Object[]} */ function comments(rules) { var c; rules = rules || []; while ((c = comment())) { if (c !== false) { rules.push(c); } } return rules; } /** * Parse comment. * * @return {Object} * @throws {Error} */ function comment() { var pos = position(); if (FORWARD_SLASH != style.charAt(0) || ASTERISK != style.charAt(1)) return; var i = 2; while ( EMPTY_STRING != style.charAt(i) && (ASTERISK != style.charAt(i) || FORWARD_SLASH != style.charAt(i + 1)) ) { ++i; } i += 2; if (EMPTY_STRING === style.charAt(i - 1)) { return error('End of comment missing'); } var str = style.slice(2, i - 2); column += 2; updatePosition(str); style = style.slice(i); column += 2; return pos({ type: TYPE_COMMENT, comment: str }); } /** * Parse declaration. * * @return {Object} * @throws {Error} */ function declaration() { var pos = position(); // prop var prop = match(PROPERTY_REGEX); if (!prop) return; comment(); // : if (!match(COLON_REGEX)) return error("property missing ':'"); // val var val = match(VALUE_REGEX); var ret = pos({ type: TYPE_DECLARATION, property: trim(prop[0].replace(COMMENT_REGEX, EMPTY_STRING)), value: val ? trim(val[0].replace(COMMENT_REGEX, EMPTY_STRING)) : EMPTY_STRING }); // ; match(SEMICOLON_REGEX); return ret; } /** * Parse declarations. * * @return {Object[]} */ function declarations() { var decls = []; comments(decls); // declarations var decl; while ((decl = declaration())) { if (decl !== false) { decls.push(decl); comments(decls); } } return decls; } whitespace(); return declarations(); }; /** * Trim `str`. * * @param {String} str * @return {String} */ function trim(str) { return str ? str.replace(TRIM_REGEX, EMPTY_STRING) : EMPTY_STRING; } /** * Parses inline style to object. * * @example * // returns { 'line-height': '42' } * StyleToObject('line-height: 42;'); * * @param {String} style - The inline style. * @param {Function} [iterator] - The iterator function. * @return {null|Object} */ function StyleToObject(style, iterator) { var output = null; if (!style || typeof style !== 'string') { return output; } var declaration; var declarations = inlineStyleParser(style); var hasIterator = typeof iterator === 'function'; var property; var value; for (var i = 0, len = declarations.length; i < len; i++) { declaration = declarations[i]; property = declaration.property; value = declaration.value; if (hasIterator) { iterator(property, value, declaration); } else if (value) { output || (output = {}); output[property] = value; } } return output; } var styleToObject = StyleToObject; var hyphenPatternRegex = /-([a-z])/g; var CUSTOM_PROPERTY_OR_NO_HYPHEN_REGEX = /^--[a-zA-Z0-9-]+$|^[^-]+$/; /** * Converts a string to camelCase. * * @param {String} string - The string. * @return {String} */ function camelCase(string) { if (typeof string !== 'string') { throw new TypeError('First argument must be a string'); } // custom property or no hyphen found if (CUSTOM_PROPERTY_OR_NO_HYPHEN_REGEX.test(string)) { return string; } // convert to camelCase return string .toLowerCase() .replace(hyphenPatternRegex, function (_, character) { return character.toUpperCase(); }); } /** * Swap key with value in an object. * * @param {Object} obj - The object. * @param {Function} [override] - The override method. * @return {Object} - The inverted object. */ function invertObject(obj, override) { if (!obj || typeof obj !== 'object') { throw new TypeError('First argument must be an object'); } var key; var value; var isOverridePresent = typeof override === 'function'; var overrides = {}; var result = {}; for (key in obj) { value = obj[key]; if (isOverridePresent) { overrides = override(key, value); if (overrides && overrides.length === 2) { result[overrides[0]] = overrides[1]; continue; } } if (typeof value === 'string') { result[value] = key; } } return result; } /** * Check if a given tag is a custom component. * * @see {@link https://github.com/facebook/react/blob/v16.6.3/packages/react-dom/src/shared/isCustomComponent.js} * * @param {string} tagName - The name of the html tag. * @param {Object} props - The props being passed to the element. * @return {boolean} */ function isCustomComponent(tagName, props) { if (tagName.indexOf('-') === -1) { return props && typeof props.is === 'string'; } switch (tagName) { // These are reserved SVG and MathML elements. // We don't mind this whitelist too much because we expect it to never grow. // The alternative is to track the namespace in a few places which is convoluted. // https://w3c.github.io/webcomponents/spec/custom/#custom-elements-core-concepts case 'annotation-xml': case 'color-profile': case 'font-face': case 'font-face-src': case 'font-face-uri': case 'font-face-format': case 'font-face-name': case 'missing-glyph': return false; default: return true; } } /** * @constant {Boolean} * @see {@link https://reactjs.org/blog/2017/09/08/dom-attributes-in-react-16.html} */ var PRESERVE_CUSTOM_ATTRIBUTES = react.version.split('.')[0] >= 16; var utilities = { PRESERVE_CUSTOM_ATTRIBUTES: PRESERVE_CUSTOM_ATTRIBUTES, camelCase: camelCase, invertObject: invertObject, isCustomComponent: isCustomComponent }; var camelCase$1 = utilities.camelCase; var htmlProperties = reactProperty.html; var svgProperties = reactProperty.svg; var isCustomAttribute = reactProperty.isCustomAttribute; var hasOwnProperty = Object.prototype.hasOwnProperty; /** * Converts HTML/SVG DOM attributes to React props. * * @param {Object} [attributes={}] - The HTML/SVG DOM attributes. * @return {Object} - The React props. */ function attributesToProps(attributes) { attributes = attributes || {}; var attributeName; var attributeNameLowerCased; var attributeValue; var property; var props = {}; for (attributeName in attributes) { attributeValue = attributes[attributeName]; // ARIA (aria-*) or custom data (data-*) attribute if (isCustomAttribute(attributeName)) { props[attributeName] = attributeValue; continue; } // convert HTML attribute to React prop attributeNameLowerCased = attributeName.toLowerCase(); if (hasOwnProperty.call(htmlProperties, attributeNameLowerCased)) { property = htmlProperties[attributeNameLowerCased]; props[property.propertyName] = property.hasBooleanValue || (property.hasOverloadedBooleanValue && !attributeValue) ? true : attributeValue; continue; } // convert SVG attribute to React prop if (hasOwnProperty.call(svgProperties, attributeName)) { property = svgProperties[attributeName]; props[property.propertyName] = attributeValue; continue; } // preserve custom attribute if React >=16 if (utilities.PRESERVE_CUSTOM_ATTRIBUTES) { props[attributeName] = attributeValue; } } // convert inline style to object if (attributes.style != null) { props.style = cssToJs(attributes.style); } return props; } /** * Converts inline CSS style to POJO (Plain Old JavaScript Object). * * @param {String} style - The inline CSS style. * @return {Object} - The style object. */ function cssToJs(style) { var styleObject = {}; if (style) { styleToObject(style, function (property, value) { // skip CSS comment if (property && value) { styleObject[camelCase$1(property)] = value; } }); } return styleObject; } var attributesToProps_1 = attributesToProps; /** * Converts DOM nodes to React elements. * * @param {DomElement[]} nodes - The DOM nodes. * @param {Object} [options={}] - The additional options. * @param {Function} [options.replace] - The replacer. * @param {Object} [options.library] - The library (React, Preact, etc.). * @return {String|ReactElement|ReactElement[]} */ function domToReact(nodes, options) { options = options || {}; var React = options.library || react; var cloneElement = React.cloneElement; var createElement = React.createElement; var isValidElement = React.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 (!shouldPassAttributesUnaltered(node)) { props = attributesToProps_1(node.attribs); } 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 attributes should be altered or not. * * @param {React.ReactElement} node * @return {Boolean} */ function shouldPassAttributesUnaltered(node) { return ( utilities.PRESERVE_CUSTOM_ATTRIBUTES && node.type === 'tag' && utilities.isCustomComponent(node.name, node.attribs) ); } var domToReact_1 = domToReact; /** * SVG elements are case-sensitive. * * @see {@link https://developer.mozilla.org/docs/Web/SVG/Element#SVG_elements_A_to_Z} */ var CASE_SENSITIVE_TAG_NAMES = [ 'animateMotion', 'animateTransform', 'clipPath', 'feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDropShadow', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussainBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence', 'foreignObject', 'linearGradient', 'radialGradient', 'textPath' ]; var constants = { CASE_SENSITIVE_TAG_NAMES: CASE_SENSITIVE_TAG_NAMES }; var CASE_SENSITIVE_TAG_NAMES$1 = constants.CASE_SENSITIVE_TAG_NAMES; var caseSensitiveTagNamesMap = {}; var tagName; for (var i = 0, len = CASE_SENSITIVE_TAG_NAMES$1.length; i < len; i++) { tagName = CASE_SENSITIVE_TAG_NAMES$1[i]; caseSensitiveTagNamesMap[tagName.toLowerCase()] = tagName; } /** * Gets case-sensitive tag name. * * @param {String} tagName - The lowercase tag name. * @return {String|undefined} */ function getCaseSensitiveTagName(tagName) { return caseSensitiveTagNamesMap[tagName]; } /** * Formats DOM attributes to a hash map. * * @param {NamedNodeMap} attributes - The list of attributes. * @return {Object} - A map of attribute name to value. */ function formatAttributes(attributes) { var result = {}; var attribute; // `NamedNodeMap` is array-like for (var i = 0, len = attributes.length; i < len; i++) { attribute = attributes[i]; result[attribute.name] = attribute.value; } return result; } /** * Corrects the tag name if it is case-sensitive (SVG). * Otherwise, returns the lowercase tag name (HTML). * * @param {String} tagName - The lowercase tag name. * @return {String} - The formatted tag name. */ function formatTagName(tagName) { tagName = tagName.toLowerCase(); var caseSensitiveTagName = getCaseSensitiveTagName(tagName); if (caseSensitiveTagName) { return caseSensitiveTagName; } return tagName; } /** * Formats the browser DOM nodes to mimic the output of `htmlparser2.parseDOM()`. * * @param {NodeList} nodes - The DOM nodes. * @param {Object} [parentObj] - The formatted parent node. * @param {String} [directive] - The directive. * @return {Object[]} - The formatted DOM object. */ function formatDOM(nodes, parentObj, directive) { parentObj = parentObj || null; var result = []; var node; var prevNode; var nodeObj; // `NodeList` is array-like for (var i = 0, len = nodes.length; i < len; i++) { node = nodes[i]; // reset nodeObj = { next: null, prev: result[i - 1] || null, parent: parentObj }; // set the next node for the previous node (if applicable) prevNode = result[i - 1]; if (prevNode) { prevNode.next = nodeObj; } // set the node name if it's not "#text" or "#comment" // e.g., "div" if (node.nodeName[0] !== '#') { nodeObj.name = formatTagName(node.nodeName); // also, nodes of type "tag" have "attribs" nodeObj.attribs = {}; // default if (node.attributes && node.attributes.length) { nodeObj.attribs = formatAttributes(node.attributes); } } // set the node type // e.g., "tag" switch (node.nodeType) { // 1 = element case 1: if (nodeObj.name === 'script' || nodeObj.name === 'style') { nodeObj.type = nodeObj.name; } else { nodeObj.type = 'tag'; } // recursively format the children nodeObj.children = formatDOM(node.childNodes, nodeObj); break; // 2 = attribute // 3 = text case 3: nodeObj.type = 'text'; nodeObj.data = node.nodeValue; break; // 8 = comment case 8: nodeObj.type = 'comment'; nodeObj.data = node.nodeValue; break; } result.push(nodeObj); } if (directive) { result.unshift({ name: directive.substring(0, directive.indexOf(' ')).toLowerCase(), data: directive, type: 'directive', next: result[0] ? result[0] : null, prev: null, parent: parentObj }); if (result[1]) { result[1].prev = result[0]; } } return result; } /** * Detects IE with or without version. * * @param {Number} [version] - The IE version to detect. * @return {Boolean} - Whether IE or the version has been detected. */ function isIE(version) { if (version) { return document.documentMode === version; } return /(MSIE |Trident\/|Edge\/)/.test(navigator.userAgent); } var utilities$1 = { formatAttributes: formatAttributes, formatDOM: formatDOM, isIE: isIE }; // constants var HTML = 'html'; var HEAD = 'head'; var BODY = 'body'; var FIRST_TAG_REGEX = /<([a-zA-Z]+[0-9]?)/; // e.g., <h1> var HEAD_TAG_REGEX = /<head.*>/i; var BODY_TAG_REGEX = /<body.*>/i; // http://www.w3.org/TR/html/syntax.html#void-elements var VOID_ELEMENTS_REGEX = /<(area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)(.*?)\/?>/gi; // detect IE browser var isIE9 = utilities$1.isIE(9); var isIE$1 = isIE9 || utilities$1.isIE(); // falls back to `parseFromString` if `createHTMLDocument` cannot be used var parseFromDocument = function () { throw new Error( 'This browser does not support `document.implementation.createHTMLDocument`' ); }; var parseFromString = function () { throw new Error( 'This browser does not support `DOMParser.prototype.parseFromString`' ); }; /** * DOMParser (performance: slow). * * @see https://developer.mozilla.org/docs/Web/API/DOMParser#Parsing_an_SVG_or_HTML_document */ if (typeof window.DOMParser === 'function') { var domParser = new window.DOMParser(); // IE9 does not support 'text/html' MIME type // https://msdn.microsoft.com/en-us/library/ff975278(v=vs.85).aspx var mimeType = isIE9 ? 'text/xml' : 'text/html'; /** * Creates an HTML document using `DOMParser.parseFromString`. * * @param {string} html - The HTML string. * @param {string} [tagName] - The element to render the HTML (with 'body' as fallback). * @return {HTMLDocument} */ parseFromString = function (html, tagName) { if (tagName) { html = '<' + tagName + '>' + html + '</' + tagName + '>'; } // because IE9 only supports MIME type 'text/xml', void elements need to be self-closed if (isIE9) { html = html.replace(VOID_ELEMENTS_REGEX, '<$1$2$3/>'); } return domParser.parseFromString(html, mimeType); }; parseFromDocument = parseFromString; } /** * DOMImplementation (performance: fair). * * @see https://developer.mozilla.org/docs/Web/API/DOMImplementation/createHTMLDocument */ if (document.implementation) { // title parameter is required in IE // https://msdn.microsoft.com/en-us/library/ff975457(v=vs.85).aspx var doc = document.implementation.createHTMLDocument( isIE$1 ? 'html-dom-parser' : undefined ); /** * Use HTML document created by `document.implementation.createHTMLDocument`. * * @param {string} html - The HTML string. * @param {string} [tagName] - The element to render the HTML (with 'body' as fallback). * @return {HTMLDocument} */ parseFromDocument = function (html, tagName) { if (tagName) { doc.documentElement.getElementsByTagName(tagName)[0].innerHTML = html; return doc; } try { doc.documentElement.innerHTML = html; return doc; // fallback when certain elements in `documentElement` are read-only (IE9) } catch (err) { if (parseFromString) { return parseFromString(html); } } }; } /** * Template (performance: fast). * * @see https://developer.mozilla.org/docs/Web/HTML/Element/template */ var template = document.createElement('template'); var parseFromTemplate; if (template.content) { /** * Uses a template element (content fragment) to parse HTML. * * @param {string} html - The HTML string. * @return {NodeList} */ parseFromTemplate = function (html) { template.innerHTML = html; return template.content.childNodes; }; } /** * Parses HTML string to DOM nodes. * * @param {string} html - The HTML string. * @return {NodeList|Array} */ function domparser(html) { var firstTagName; var match = html.match(FIRST_TAG_REGEX); if (match && match[1]) { firstTagName = match[1].toLowerCase(); } var doc; var element; var elements; switch (firstTagName) { case HTML: doc = parseFromString(html); // the created document may come with filler head/body elements, // so make sure to remove them if they don't actually exist if (!HEAD_TAG_REGEX.test(html)) { element = doc.getElementsByTagName(HEAD)[0]; if (element) { element.parentNode.removeChild(element); } } if (!BODY_TAG_REGEX.test(html)) { element = doc.getElementsByTagName(BODY)[0]; if (element) { element.parentNode.removeChild(element); } } return doc.getElementsByTagName(HTML); case HEAD: case BODY: elements = parseFromDocument(html).getElementsByTagName(firstTagName); // if there's a sibling element, then return both elements if (BODY_TAG_REGEX.test(html) && HEAD_TAG_REGEX.test(html)) { return elements[0].parentNode.childNodes; } return elements; // low-level tag or text default: if (parseFromTemplate) { return parseFromTemplate(html); } return parseFromDocument(html, BODY).getElementsByTagName(BODY)[0] .childNodes; } } var domparser_1 = domparser; var formatDOM$1 = utilities$1.formatDOM; var isIE9$1 = utilities$1.isIE(9); var DIRECTIVE_REGEX = /<(![a-zA-Z\s]+)>/; // e.g., <!doctype html> /** * Parses HTML and reformats DOM nodes output. * * @param {String} html - The HTML string. * @return {Array} - The formatted DOM nodes. */ function parseDOM(html) { if (typeof html !== 'string') { throw new TypeError('First argument must be a string'); } if (!html) { return []; } // match directive var match = html.match(DIRECTIVE_REGEX); var directive; if (match && match[1]) { directive = match[1]; // remove directive in IE9 because DOMParser uses // MIME type 'text/xml' instead of 'text/html' if (isIE9$1) { html = html.replace(match[0], ''); } } return formatDOM$1(domparser_1(html), null, directive); } var htmlToDomClient = parseDOM; // decode HTML entities by default for `htmlparser2` var domParserOptions = { decodeEntities: true, lowerCaseAttributeNames: false }; /** * Converts HTML string to React elements. * * @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') { throw new TypeError('First argument must be a string'); } if (html === '') { return []; } options = options || {}; return domToReact_1( htmlToDomClient(html, options.htmlparser2 || domParserOptions), options ); } HTMLReactParser.domToReact = domToReact_1; HTMLReactParser.htmlToDOM = htmlToDomClient; HTMLReactParser.attributesToProps = attributesToProps_1; // support CommonJS and ES Modules var htmlReactParser = HTMLReactParser; var _default = HTMLReactParser; htmlReactParser.default = _default; return htmlReactParser; }))); //# sourceMappingURL=html-react-parser.js.map