html-react-parser 0.10.5 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -106,11 +106,11 @@ parse(
106
106
 
107
107
  The `replace` callback allows you to swap an element with another React element.
108
108
 
109
- 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):
109
+ 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):
110
110
 
111
111
  ```js
112
112
  parse('<br>', {
113
- replace: function(domNode) {
113
+ replace: function (domNode) {
114
114
  console.dir(domNode, { depth: null });
115
115
  }
116
116
  });
@@ -224,6 +224,60 @@ parse('<br>', {
224
224
  });
225
225
  ```
226
226
 
227
+ ### htmlparser2
228
+
229
+ This library passes the following options to [htmlparser2](https://github.com/fb55/htmlparser2/tree/v3.10.1) on the server-side:
230
+
231
+ ```js
232
+ {
233
+ decodeEntities: true,
234
+ lowerCaseAttributeNames: false
235
+ }
236
+ ```
237
+
238
+ By passing your own options, the default library options will be **replaced** (not merged).
239
+
240
+ As a result, to enable `decodeEntities` and `xmlMode`, you need to do the following:
241
+
242
+ ```js
243
+ parse('<p /><p />', {
244
+ htmlparser2: {
245
+ decodeEntities: true,
246
+ xmlMode: true
247
+ }
248
+ });
249
+ ```
250
+
251
+ See [htmlparser2 options](https://github.com/fb55/htmlparser2/wiki/Parser-options).
252
+
253
+ > **Warning**: By overriding htmlparser2 options, there's a chance of breaking universal rendering. Do this at your own risk.
254
+
255
+ ### trim
256
+
257
+ Normally, whitespace is preserved:
258
+
259
+ ```js
260
+ parse('<br>\n'); // [React.createElement('br'), '\n']
261
+ ```
262
+
263
+ By enabling the `trim` option, whitespace text nodes will be skipped:
264
+
265
+ ```js
266
+ parse('<br>\n', { trim: true }); // React.createElement('br')
267
+ ```
268
+
269
+ This addresses the warning:
270
+
271
+ ```
272
+ Warning: validateDOMNesting(...): Whitespace text nodes cannot appear as a child of <table>. Make sure you don't have any extra whitespace between tags on each line of your source code.
273
+ ```
274
+
275
+ However, this option may strip out intentional whitespace:
276
+
277
+ ```js
278
+ parse('<p> </p>', { trim: true }); // React.createElement('p')
279
+ ```
280
+
227
281
  ## FAQ
228
282
 
229
283
  #### Is this library XSS safe?
@@ -250,6 +304,41 @@ Check if your arguments are valid. Also, see ["Does this library sanitize invali
250
304
 
251
305
  Yes, this library supports server-side rendering on Node.js. See [demo](https://repl.it/@remarkablemark/html-react-parser-SSR).
252
306
 
307
+ #### Why are my elements nested incorrectly?
308
+
309
+ Make sure your [HTML markup is valid](https://validator.w3.org/). The HTML to DOM parsing will be affected if you're using self-closing syntax (`/>`) on non-void elements:
310
+
311
+ ```js
312
+ parse('<div /><div />'); // returns single element instead of array of elements
313
+ ```
314
+
315
+ See [#158](https://github.com/remarkablemark/html-react-parser/issues/158).
316
+
317
+ #### I get "Warning: validateDOMNesting(...): Whitespace text nodes cannot appear as a child of table."
318
+
319
+ Enable the [trim](https://github.com/remarkablemark/html-react-parser#trim) option. See [#155](https://github.com/remarkablemark/html-react-parser/issues/155).
320
+
321
+ #### Don't change case of tags.
322
+
323
+ Tags are lowercased by default. To prevent that from happening, pass the [htmlparser2 option](https://github.com/remarkablemark/html-react-parser#htmlparser2):
324
+
325
+ ```js
326
+ const options = {
327
+ htmlparser2: {
328
+ lowerCaseTags: false
329
+ }
330
+ };
331
+ parse('<CustomElement>', options); // React.createElement('CustomElement')
332
+ ```
333
+
334
+ > **Warning**: By preserving case-sensitivity of the tags, you may get rendering warnings like:
335
+ >
336
+ > ```
337
+ > Warning: <CustomElement> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.
338
+ > ```
339
+
340
+ See [#62](https://github.com/remarkablemark/html-react-parser/issues/62) and [example](https://repl.it/@remarkablemark/html-react-parser-62).
341
+
253
342
  ## Benchmarks
254
343
 
255
344
  ```sh
@@ -297,6 +386,7 @@ Support this project with your organization. Your logo will show up here with a
297
386
 
298
387
  ## Support
299
388
 
389
+ - [GitHub Sponsors](https://b.remarkabl.org/github-sponsors)
300
390
  - [Patreon](https://b.remarkabl.org/patreon)
301
391
  - [Open Collective](https://b.remarkabl.org/open-collective-html-react-parser)
302
392
  - [Ko-fi](https://b.remarkabl.org/ko-fi)
@@ -1124,6 +1124,8 @@
1124
1124
  var replaceElement;
1125
1125
  var props;
1126
1126
  var children;
1127
+ var data;
1128
+ var trim = options.trim;
1127
1129
 
1128
1130
  for (var i = 0, len = nodes.length; i < len; i++) {
1129
1131
  node = nodes[i];
@@ -1133,7 +1135,7 @@
1133
1135
  replaceElement = options.replace(node);
1134
1136
 
1135
1137
  if (isValidElement(replaceElement)) {
1136
- // specify a "key" prop if element has siblings
1138
+ // set "key" prop for sibling elements
1137
1139
  // https://fb.me/react-warning-keys
1138
1140
  if (len > 1) {
1139
1141
  replaceElement = cloneElement(replaceElement, {
@@ -1146,45 +1148,54 @@
1146
1148
  }
1147
1149
 
1148
1150
  if (node.type === 'text') {
1149
- result.push(node.data);
1151
+ // if trim option is enabled, skip whitespace text nodes
1152
+ if (trim) {
1153
+ data = node.data.trim();
1154
+ if (data) {
1155
+ result.push(node.data);
1156
+ }
1157
+ } else {
1158
+ result.push(node.data);
1159
+ }
1150
1160
  continue;
1151
1161
  }
1152
1162
 
1153
1163
  props = node.attribs;
1154
1164
  if (!shouldPassAttributesUnaltered(node)) {
1155
- // update values
1156
1165
  props = attributesToProps_1(node.attribs);
1157
1166
  }
1158
1167
 
1159
1168
  children = null;
1160
1169
 
1161
- // node type for <script> is "script"
1162
- // node type for <style> is "style"
1163
- if (node.type === 'script' || node.type === 'style') {
1164
- // prevent text in <script> or <style> from being escaped
1165
- // https://facebook.github.io/react/tips/dangerously-set-inner-html.html
1166
- if (node.children[0]) {
1167
- props.dangerouslySetInnerHTML = {
1168
- __html: node.children[0].data
1169
- };
1170
- }
1171
- } else if (node.type === 'tag') {
1172
- // setting textarea value in children is an antipattern in React
1173
- // https://reactjs.org/docs/forms.html#the-textarea-tag
1174
- if (node.name === 'textarea' && node.children[0]) {
1175
- props.defaultValue = node.children[0].data;
1176
-
1177
- // continue recursion of creating React elements (if applicable)
1178
- } else if (node.children && node.children.length) {
1179
- children = domToReact(node.children, options);
1180
- }
1170
+ switch (node.type) {
1171
+ case 'script':
1172
+ case 'style':
1173
+ // prevent text in <script> or <style> from being escaped
1174
+ // https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
1175
+ if (node.children[0]) {
1176
+ props.dangerouslySetInnerHTML = {
1177
+ __html: node.children[0].data
1178
+ };
1179
+ }
1180
+ break;
1181
+
1182
+ case 'tag':
1183
+ // setting textarea value in children is an antipattern in React
1184
+ // https://reactjs.org/docs/forms.html#the-textarea-tag
1185
+ if (node.name === 'textarea' && node.children[0]) {
1186
+ props.defaultValue = node.children[0].data;
1187
+ } else if (node.children && node.children.length) {
1188
+ // continue recursion of creating React elements (if applicable)
1189
+ children = domToReact(node.children, options);
1190
+ }
1191
+ break;
1181
1192
 
1182
1193
  // skip all other cases (e.g., comment)
1183
- } else {
1184
- continue;
1194
+ default:
1195
+ continue;
1185
1196
  }
1186
1197
 
1187
- // specify a "key" prop if element has siblings
1198
+ // set "key" prop for sibling elements
1188
1199
  // https://fb.me/react-warning-keys
1189
1200
  if (len > 1) {
1190
1201
  props.key = i;
@@ -1197,6 +1208,8 @@
1197
1208
  }
1198
1209
 
1199
1210
  /**
1211
+ * Determines whether attributes should be altered or not.
1212
+ *
1200
1213
  * @param {React.ReactElement} node
1201
1214
  * @return {Boolean}
1202
1215
  */
@@ -1428,13 +1441,24 @@
1428
1441
  var isIE9 = utilities$1.isIE(9);
1429
1442
  var isIE$1 = isIE9 || utilities$1.isIE();
1430
1443
 
1444
+ // falls back to `parseFromString` if `createHTMLDocument` cannot be used
1445
+ var parseFromDocument = function () {
1446
+ throw new Error(
1447
+ 'This browser does not support `document.implementation.createHTMLDocument`'
1448
+ );
1449
+ };
1450
+
1451
+ var parseFromString = function () {
1452
+ throw new Error(
1453
+ 'This browser does not support `DOMParser.prototype.parseFromString`'
1454
+ );
1455
+ };
1456
+
1431
1457
  /**
1432
1458
  * DOMParser (performance: slow).
1433
1459
  *
1434
1460
  * @see https://developer.mozilla.org/docs/Web/API/DOMParser#Parsing_an_SVG_or_HTML_document
1435
1461
  */
1436
- var parseFromString;
1437
-
1438
1462
  if (typeof window.DOMParser === 'function') {
1439
1463
  var domParser = new window.DOMParser();
1440
1464
 
@@ -1449,7 +1473,7 @@
1449
1473
  * @param {string} [tagName] - The element to render the HTML (with 'body' as fallback).
1450
1474
  * @return {HTMLDocument}
1451
1475
  */
1452
- parseFromString = function domStringParser(html, tagName) {
1476
+ parseFromString = function (html, tagName) {
1453
1477
  if (tagName) {
1454
1478
  html = '<' + tagName + '>' + html + '</' + tagName + '>';
1455
1479
  }
@@ -1461,6 +1485,8 @@
1461
1485
 
1462
1486
  return domParser.parseFromString(html, mimeType);
1463
1487
  };
1488
+
1489
+ parseFromDocument = parseFromString;
1464
1490
  }
1465
1491
 
1466
1492
  /**
@@ -1468,13 +1494,11 @@
1468
1494
  *
1469
1495
  * @see https://developer.mozilla.org/docs/Web/API/DOMImplementation/createHTMLDocument
1470
1496
  */
1471
- var parseFromDocument;
1472
-
1473
- if (typeof document.implementation === 'object') {
1497
+ if (document.implementation) {
1474
1498
  // title parameter is required in IE
1475
1499
  // https://msdn.microsoft.com/en-us/library/ff975457(v=vs.85).aspx
1476
1500
  var doc = document.implementation.createHTMLDocument(
1477
- isIE$1 ? 'HTML_DOM_PARSER_TITLE' : undefined
1501
+ isIE$1 ? 'html-dom-parser' : undefined
1478
1502
  );
1479
1503
 
1480
1504
  /**
@@ -1484,7 +1508,7 @@
1484
1508
  * @param {string} [tagName] - The element to render the HTML (with 'body' as fallback).
1485
1509
  * @return {HTMLDocument}
1486
1510
  */
1487
- parseFromDocument = function createHTMLDocument(html, tagName) {
1511
+ parseFromDocument = function (html, tagName) {
1488
1512
  if (tagName) {
1489
1513
  doc.documentElement.getElementsByTagName(tagName)[0].innerHTML = html;
1490
1514
  return doc;
@@ -1507,8 +1531,8 @@
1507
1531
  *
1508
1532
  * @see https://developer.mozilla.org/docs/Web/HTML/Element/template
1509
1533
  */
1510
- var parseFromTemplate;
1511
1534
  var template = document.createElement('template');
1535
+ var parseFromTemplate;
1512
1536
 
1513
1537
  if (template.content) {
1514
1538
  /**
@@ -1517,15 +1541,12 @@
1517
1541
  * @param {string} html - The HTML string.
1518
1542
  * @return {NodeList}
1519
1543
  */
1520
- parseFromTemplate = function templateParser(html) {
1544
+ parseFromTemplate = function (html) {
1521
1545
  template.innerHTML = html;
1522
1546
  return template.content.childNodes;
1523
1547
  };
1524
1548
  }
1525
1549
 
1526
- // fallback document parser
1527
- var parseWithFallback = parseFromDocument || parseFromString;
1528
-
1529
1550
  /**
1530
1551
  * Parses HTML string to DOM nodes.
1531
1552
  *
@@ -1546,42 +1567,35 @@
1546
1567
 
1547
1568
  switch (firstTagName) {
1548
1569
  case HTML:
1549
- if (parseFromString) {
1550
- doc = parseFromString(html);
1551
-
1552
- // the created document may come with filler head/body elements,
1553
- // so make sure to remove them if they don't actually exist
1554
- if (!HEAD_TAG_REGEX.test(html)) {
1555
- element = doc.getElementsByTagName(HEAD)[0];
1556
- if (element) {
1557
- element.parentNode.removeChild(element);
1558
- }
1570
+ doc = parseFromString(html);
1571
+
1572
+ // the created document may come with filler head/body elements,
1573
+ // so make sure to remove them if they don't actually exist
1574
+ if (!HEAD_TAG_REGEX.test(html)) {
1575
+ element = doc.getElementsByTagName(HEAD)[0];
1576
+ if (element) {
1577
+ element.parentNode.removeChild(element);
1559
1578
  }
1579
+ }
1560
1580
 
1561
- if (!BODY_TAG_REGEX.test(html)) {
1562
- element = doc.getElementsByTagName(BODY)[0];
1563
- if (element) {
1564
- element.parentNode.removeChild(element);
1565
- }
1581
+ if (!BODY_TAG_REGEX.test(html)) {
1582
+ element = doc.getElementsByTagName(BODY)[0];
1583
+ if (element) {
1584
+ element.parentNode.removeChild(element);
1566
1585
  }
1567
-
1568
- return doc.getElementsByTagName(HTML);
1569
1586
  }
1570
- break;
1587
+
1588
+ return doc.getElementsByTagName(HTML);
1571
1589
 
1572
1590
  case HEAD:
1573
1591
  case BODY:
1574
- if (parseWithFallback) {
1575
- elements = parseWithFallback(html).getElementsByTagName(firstTagName);
1592
+ elements = parseFromDocument(html).getElementsByTagName(firstTagName);
1576
1593
 
1577
- // account for possibility of sibling
1578
- if (BODY_TAG_REGEX.test(html) && HEAD_TAG_REGEX.test(html)) {
1579
- return elements[0].parentNode.childNodes;
1580
- }
1581
-
1582
- return elements;
1594
+ // if there's a sibling element, then return both elements
1595
+ if (BODY_TAG_REGEX.test(html) && HEAD_TAG_REGEX.test(html)) {
1596
+ return elements[0].parentNode.childNodes;
1583
1597
  }
1584
- break;
1598
+ return elements;
1585
1599
 
1586
1600
  // low-level tag or text
1587
1601
  default:
@@ -1589,15 +1603,9 @@
1589
1603
  return parseFromTemplate(html);
1590
1604
  }
1591
1605
 
1592
- if (parseWithFallback) {
1593
- return parseWithFallback(html, BODY).getElementsByTagName(BODY)[0]
1594
- .childNodes;
1595
- }
1596
-
1597
- break;
1606
+ return parseFromDocument(html, BODY).getElementsByTagName(BODY)[0]
1607
+ .childNodes;
1598
1608
  }
1599
-
1600
- return [];
1601
1609
  }
1602
1610
 
1603
1611
  var domparser_1 = domparser;
@@ -1641,19 +1649,31 @@
1641
1649
 
1642
1650
  var htmlToDomClient = parseDOM;
1643
1651
 
1652
+ // decode HTML entities by default for `htmlparser2`
1653
+ var domParserOptions = { decodeEntities: true, lowerCaseAttributeNames: false };
1654
+
1644
1655
  /**
1645
1656
  * Converts HTML string to React elements.
1646
1657
  *
1647
- * @param {String} html - The HTML string to parse to React.
1648
- * @param {Object} [options] - The parser options.
1649
- * @param {Function} [options.replace] - The replace method.
1650
- * @return {JSX.Element|JSX.Element[]|String} - Returns React element(s), string, or empty array.
1658
+ * @param {String} html - HTML string.
1659
+ * @param {Object} [options] - Parser options.
1660
+ * @param {Object} [options.htmlparser2] - htmlparser2 options.
1661
+ * @param {Object} [options.library] - Library for React, Preact, etc.
1662
+ * @param {Function} [options.replace] - Replace method.
1663
+ * @return {JSX.Element|JSX.Element[]|String} - React element(s), empty array, or string.
1651
1664
  */
1652
1665
  function HTMLReactParser(html, options) {
1653
1666
  if (typeof html !== 'string') {
1654
1667
  throw new TypeError('First argument must be a string');
1655
1668
  }
1656
- return domToReact_1(htmlToDomClient(html), options);
1669
+ if (html === '') {
1670
+ return [];
1671
+ }
1672
+ options = options || {};
1673
+ return domToReact_1(
1674
+ htmlToDomClient(html, options.htmlparser2 || domParserOptions),
1675
+ options
1676
+ );
1657
1677
  }
1658
1678
 
1659
1679
  HTMLReactParser.domToReact = domToReact_1;
@@ -1661,8 +1681,8 @@
1661
1681
 
1662
1682
  // support CommonJS and ES Modules
1663
1683
  var htmlReactParser = HTMLReactParser;
1664
- var default_1 = HTMLReactParser;
1665
- htmlReactParser.default = default_1;
1684
+ var _default = HTMLReactParser;
1685
+ htmlReactParser.default = _default;
1666
1686
 
1667
1687
  return htmlReactParser;
1668
1688