Skip to content

Commit 3684196

Browse files
Merge pull request #394 from vidhu/fix/trimming-issue
fix: trim only when necessary if trimming is off
2 parents 7c6e023 + 4126592 commit 3684196

File tree

5 files changed

+95
-23
lines changed

5 files changed

+95
-23
lines changed

README.md

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ parse('<p>Hello, World!</p>'); // React.createElement('p', {}, 'Hello, World!')
5050
- [Parser throws an error](#parser-throws-an-error)
5151
- [Is SSR supported?](#is-ssr-supported)
5252
- [Elements aren't nested correctly](#elements-arent-nested-correctly)
53-
- [Warning: validateDOMNesting(...): Whitespace text nodes cannot appear as a child of table](#warning-validatedomnesting-whitespace-text-nodes-cannot-appear-as-a-child-of-table)
5453
- [Don't change case of tags](#dont-change-case-of-tags)
5554
- [TS Error: Property 'attribs' does not exist on type 'DOMNode'](#ts-error-property-attribs-does-not-exist-on-type-domnode)
5655
- [Can I enable `trim` for certain elements?](#can-i-enable-trim-for-certain-elements)
@@ -351,16 +350,16 @@ By default, whitespace is preserved:
351350
parse('<br>\n'); // [React.createElement('br'), '\n']
352351
```
353352

354-
To remove whitespace, enable the `trim` option:
353+
But certain elements like `<table>` will strip out invalid whitespace:
355354

356355
```js
357-
parse('<br>\n', { trim: true }); // React.createElement('br')
356+
parse('<table>\n</table>'); // React.createElement('table')
358357
```
359358

360-
This fixes the warning:
359+
To remove whitespace, enable the `trim` option:
361360

362-
```
363-
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.
361+
```js
362+
parse('<br>\n', { trim: true }); // React.createElement('br')
364363
```
365364

366365
However, intentional whitespace may be stripped out:
@@ -427,10 +426,6 @@ parse('<div /><div />'); // returns single element instead of array of elements
427426

428427
See [#158](https://github.com/remarkablemark/html-react-parser/issues/158).
429428

430-
### Warning: validateDOMNesting(...): Whitespace text nodes cannot appear as a child of table
431-
432-
Enable the [trim](#trim) option. See [#155](https://github.com/remarkablemark/html-react-parser/issues/155).
433-
434429
### Don't change case of tags
435430

436431
Tags are lowercased by default. To prevent that from happening, pass the [htmlparser2 option](#htmlparser2):

lib/dom-to-react.js

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ var attributesToProps = require('./attributes-to-props');
33
var utilities = require('./utilities');
44

55
var setStyleProp = utilities.setStyleProp;
6+
var canTextBeChildOfNode = utilities.canTextBeChildOfNode;
67

78
/**
89
* Converts DOM nodes to JSX element(s).
@@ -23,11 +24,11 @@ function domToReact(nodes, options) {
2324

2425
var result = [];
2526
var node;
27+
var isWhitespace;
2628
var hasReplace = typeof options.replace === 'function';
2729
var replaceElement;
2830
var props;
2931
var children;
30-
var data;
3132
var trim = options.trim;
3233

3334
for (var i = 0, len = nodes.length; i < len; i++) {
@@ -51,15 +52,23 @@ function domToReact(nodes, options) {
5152
}
5253

5354
if (node.type === 'text') {
54-
// if trim option is enabled, skip whitespace text nodes
55-
if (trim) {
56-
data = node.data.trim();
57-
if (data) {
58-
result.push(node.data);
59-
}
60-
} else {
61-
result.push(node.data);
55+
isWhitespace = !node.data.trim().length;
56+
57+
if (isWhitespace && node.parent && !canTextBeChildOfNode(node.parent)) {
58+
// We have a whitespace node that can't be nested in its parent
59+
// so skip it
60+
continue;
6261
}
62+
63+
if (trim && isWhitespace) {
64+
// Trim is enabled and we have a whitespace node
65+
// so skip it
66+
continue;
67+
}
68+
69+
// We have a text node that's not whitespace and it can be nested
70+
// in its parent so add it to the results
71+
result.push(node.data);
6372
continue;
6473
}
6574

lib/utilities.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,35 @@ function setStyleProp(style, props) {
9696
*/
9797
var PRESERVE_CUSTOM_ATTRIBUTES = React.version.split('.')[0] >= 16;
9898

99+
// Taken from
100+
// https://github.com/facebook/react/blob/cae635054e17a6f107a39d328649137b83f25972/packages/react-dom/src/client/validateDOMNesting.js#L213
101+
var elementsWithNoTextChildren = new Set([
102+
'tr',
103+
'tbody',
104+
'thead',
105+
'tfoot',
106+
'colgroup',
107+
'table',
108+
'head',
109+
'html',
110+
'frameset'
111+
]);
112+
113+
/**
114+
* Checks if the given node can contain text nodes
115+
*
116+
* @param {DomElement} node
117+
* @returns {boolean}
118+
*/
119+
function canTextBeChildOfNode(node) {
120+
return !elementsWithNoTextChildren.has(node.name);
121+
}
122+
99123
module.exports = {
100124
PRESERVE_CUSTOM_ATTRIBUTES: PRESERVE_CUSTOM_ATTRIBUTES,
101125
invertObject: invertObject,
102126
isCustomComponent: isCustomComponent,
103-
setStyleProp: setStyleProp
127+
setStyleProp: setStyleProp,
128+
canTextBeChildOfNode: canTextBeChildOfNode,
129+
elementsWithNoTextChildren: elementsWithNoTextChildren
104130
};

test/index.test.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,13 +138,34 @@ describe('htmlparser2 option', () => {
138138
});
139139

140140
describe('trim option', () => {
141-
it('preserves whitespace text nodes when disabled (default)', () => {
141+
it('preserves whitespace text nodes when disabled if valid in parent (default)', () => {
142142
const html = `<table>
143143
<tbody>
144+
<tr><td>hello</td><td>\n</td><td>&nbsp;</td>\t</tr>\r
144145
</tbody>
145146
</table>`;
146147
const reactElement = parse(html);
147-
expect(render(reactElement)).toBe(html);
148+
expect(render(reactElement)).toBe(
149+
'<table><tbody><tr><td>hello</td><td>\n</td><td>\u00a0</td></tr></tbody></table>'
150+
);
151+
expect(reactElement).toMatchInlineSnapshot(`
152+
<table>
153+
<tbody>
154+
<tr>
155+
<td>
156+
hello
157+
</td>
158+
<td>
159+
160+
161+
</td>
162+
<td>
163+
\u00a0
164+
</td>
165+
</tr>
166+
</tbody>
167+
</table>
168+
`);
148169
});
149170

150171
it('removes whitespace text nodes when enabled', () => {

test/utilities.test.js

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ const {
33
PRESERVE_CUSTOM_ATTRIBUTES,
44
invertObject,
55
isCustomComponent,
6-
setStyleProp
6+
setStyleProp,
7+
elementsWithNoTextChildren,
8+
canTextBeChildOfNode
79
} = require('../lib/utilities');
810

911
describe('invertObject', () => {
@@ -125,3 +127,22 @@ describe('setStyleProp', () => {
125127
expect(props).toEqual({ style: {} });
126128
});
127129
});
130+
131+
describe('canTextBeChildOfNode', () => {
132+
it.each(Array.from(elementsWithNoTextChildren))(
133+
'returns false since text node cannot be child of %s',
134+
nodeName => {
135+
const node = {
136+
name: nodeName
137+
};
138+
expect(canTextBeChildOfNode(node)).toBe(false);
139+
}
140+
);
141+
142+
it('returns true if text can be child of <td/>', () => {
143+
const node = {
144+
name: 'td'
145+
};
146+
expect(canTextBeChildOfNode(node)).toBe(true);
147+
});
148+
});

0 commit comments

Comments
 (0)