Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@wordpress/dom

Package Overview
Dependencies
Maintainers
15
Versions
190
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@wordpress/dom - npm Package Compare versions

Comparing version 2.13.1 to 2.14.0

build-module/phrasing-content.js

177

build-module/dom.js
/**
* External dependencies
*/
import { includes } from 'lodash';
import { includes, noop } from 'lodash';
/**
* Internal dependencies
*/
import { isPhrasingContent } from './phrasing-content';
/**
* Browser dependencies

@@ -703,2 +708,172 @@ */

}
/**
* Given a schema, unwraps or removes nodes, attributes and classes on a node
* list.
*
* @param {NodeList} nodeList The nodeList to filter.
* @param {Document} doc The document of the nodeList.
* @param {Object} schema An array of functions that can mutate with the provided node.
* @param {Object} inline Whether to clean for inline mode.
*/
function cleanNodeList(nodeList, doc, schema, inline) {
Array.from(nodeList).forEach(function (node) {
var tag = node.nodeName.toLowerCase(); // It's a valid child, if the tag exists in the schema without an isMatch
// function, or with an isMatch function that matches the node.
if (schema.hasOwnProperty(tag) && (!schema[tag].isMatch || schema[tag].isMatch(node))) {
if (node.nodeType === ELEMENT_NODE) {
var _schema$tag = schema[tag],
_schema$tag$attribute = _schema$tag.attributes,
attributes = _schema$tag$attribute === void 0 ? [] : _schema$tag$attribute,
_schema$tag$classes = _schema$tag.classes,
classes = _schema$tag$classes === void 0 ? [] : _schema$tag$classes,
children = _schema$tag.children,
_schema$tag$require = _schema$tag.require,
require = _schema$tag$require === void 0 ? [] : _schema$tag$require,
allowEmpty = _schema$tag.allowEmpty; // If the node is empty and it's supposed to have children,
// remove the node.
if (children && !allowEmpty && isEmpty(node)) {
remove(node);
return;
}
if (node.hasAttributes()) {
// Strip invalid attributes.
Array.from(node.attributes).forEach(function (_ref) {
var name = _ref.name;
if (name !== 'class' && !includes(attributes, name)) {
node.removeAttribute(name);
}
}); // Strip invalid classes.
// In jsdom-jscore, 'node.classList' can be undefined.
// TODO: Explore patching this in jsdom-jscore.
if (node.classList && node.classList.length) {
var mattchers = classes.map(function (item) {
if (typeof item === 'string') {
return function (className) {
return className === item;
};
} else if (item instanceof RegExp) {
return function (className) {
return item.test(className);
};
}
return noop;
});
Array.from(node.classList).forEach(function (name) {
if (!mattchers.some(function (isMatch) {
return isMatch(name);
})) {
node.classList.remove(name);
}
});
if (!node.classList.length) {
node.removeAttribute('class');
}
}
}
if (node.hasChildNodes()) {
// Do not filter any content.
if (children === '*') {
return;
} // Continue if the node is supposed to have children.
if (children) {
// If a parent requires certain children, but it does
// not have them, drop the parent and continue.
if (require.length && !node.querySelector(require.join(','))) {
cleanNodeList(node.childNodes, doc, schema, inline);
unwrap(node); // If the node is at the top, phrasing content, and
// contains children that are block content, unwrap
// the node because it is invalid.
} else if (node.parentNode.nodeName === 'BODY' && isPhrasingContent(node)) {
cleanNodeList(node.childNodes, doc, schema, inline);
if (Array.from(node.childNodes).some(function (child) {
return !isPhrasingContent(child);
})) {
unwrap(node);
}
} else {
cleanNodeList(node.childNodes, doc, children, inline);
} // Remove children if the node is not supposed to have any.
} else {
while (node.firstChild) {
remove(node.firstChild);
}
}
}
} // Invalid child. Continue with schema at the same place and unwrap.
} else {
cleanNodeList(node.childNodes, doc, schema, inline); // For inline mode, insert a line break when unwrapping nodes that
// are not phrasing content.
if (inline && !isPhrasingContent(node) && node.nextElementSibling) {
insertAfter(doc.createElement('br'), node);
}
unwrap(node);
}
});
}
/**
* Recursively checks if an element is empty. An element is not empty if it
* contains text or contains elements with attributes such as images.
*
* @param {Element} element The element to check.
*
* @return {boolean} Wether or not the element is empty.
*/
export function isEmpty(element) {
if (!element.hasChildNodes()) {
return true;
}
return Array.from(element.childNodes).every(function (node) {
if (node.nodeType === TEXT_NODE) {
return !node.nodeValue.trim();
}
if (node.nodeType === ELEMENT_NODE) {
if (node.nodeName === 'BR') {
return true;
} else if (node.hasAttributes()) {
return false;
}
return isEmpty(node);
}
return true;
});
}
/**
* Given a schema, unwraps or removes nodes, attributes and classes on HTML.
*
* @param {string} HTML The HTML to clean up.
* @param {Object} schema Schema for the HTML.
* @param {Object} inline Whether to clean for inline mode.
*
* @return {string} The cleaned up HTML.
*/
export function removeInvalidHTML(HTML, schema, inline) {
var doc = document.implementation.createHTMLDocument('');
doc.body.innerHTML = HTML;
cleanNodeList(doc.body.childNodes, doc, schema, inline);
return doc.body.innerHTML;
}
//# sourceMappingURL=dom.js.map

@@ -16,2 +16,3 @@ /**

export * from './dom';
export * from './phrasing-content';
//# sourceMappingURL=index.js.map

@@ -27,5 +27,9 @@ "use strict";

exports.__unstableStripHTML = __unstableStripHTML;
exports.isEmpty = isEmpty;
exports.removeInvalidHTML = removeInvalidHTML;
var _lodash = require("lodash");
var _phrasingContent = require("./phrasing-content");
/**

@@ -36,2 +40,6 @@ * External dependencies

/**
* Internal dependencies
*/
/**
* Browser dependencies

@@ -753,2 +761,174 @@ */

}
/**
* Given a schema, unwraps or removes nodes, attributes and classes on a node
* list.
*
* @param {NodeList} nodeList The nodeList to filter.
* @param {Document} doc The document of the nodeList.
* @param {Object} schema An array of functions that can mutate with the provided node.
* @param {Object} inline Whether to clean for inline mode.
*/
function cleanNodeList(nodeList, doc, schema, inline) {
Array.from(nodeList).forEach(function (node) {
var tag = node.nodeName.toLowerCase(); // It's a valid child, if the tag exists in the schema without an isMatch
// function, or with an isMatch function that matches the node.
if (schema.hasOwnProperty(tag) && (!schema[tag].isMatch || schema[tag].isMatch(node))) {
if (node.nodeType === ELEMENT_NODE) {
var _schema$tag = schema[tag],
_schema$tag$attribute = _schema$tag.attributes,
attributes = _schema$tag$attribute === void 0 ? [] : _schema$tag$attribute,
_schema$tag$classes = _schema$tag.classes,
classes = _schema$tag$classes === void 0 ? [] : _schema$tag$classes,
children = _schema$tag.children,
_schema$tag$require = _schema$tag.require,
require = _schema$tag$require === void 0 ? [] : _schema$tag$require,
allowEmpty = _schema$tag.allowEmpty; // If the node is empty and it's supposed to have children,
// remove the node.
if (children && !allowEmpty && isEmpty(node)) {
remove(node);
return;
}
if (node.hasAttributes()) {
// Strip invalid attributes.
Array.from(node.attributes).forEach(function (_ref) {
var name = _ref.name;
if (name !== 'class' && !(0, _lodash.includes)(attributes, name)) {
node.removeAttribute(name);
}
}); // Strip invalid classes.
// In jsdom-jscore, 'node.classList' can be undefined.
// TODO: Explore patching this in jsdom-jscore.
if (node.classList && node.classList.length) {
var mattchers = classes.map(function (item) {
if (typeof item === 'string') {
return function (className) {
return className === item;
};
} else if (item instanceof RegExp) {
return function (className) {
return item.test(className);
};
}
return _lodash.noop;
});
Array.from(node.classList).forEach(function (name) {
if (!mattchers.some(function (isMatch) {
return isMatch(name);
})) {
node.classList.remove(name);
}
});
if (!node.classList.length) {
node.removeAttribute('class');
}
}
}
if (node.hasChildNodes()) {
// Do not filter any content.
if (children === '*') {
return;
} // Continue if the node is supposed to have children.
if (children) {
// If a parent requires certain children, but it does
// not have them, drop the parent and continue.
if (require.length && !node.querySelector(require.join(','))) {
cleanNodeList(node.childNodes, doc, schema, inline);
unwrap(node); // If the node is at the top, phrasing content, and
// contains children that are block content, unwrap
// the node because it is invalid.
} else if (node.parentNode.nodeName === 'BODY' && (0, _phrasingContent.isPhrasingContent)(node)) {
cleanNodeList(node.childNodes, doc, schema, inline);
if (Array.from(node.childNodes).some(function (child) {
return !(0, _phrasingContent.isPhrasingContent)(child);
})) {
unwrap(node);
}
} else {
cleanNodeList(node.childNodes, doc, children, inline);
} // Remove children if the node is not supposed to have any.
} else {
while (node.firstChild) {
remove(node.firstChild);
}
}
}
} // Invalid child. Continue with schema at the same place and unwrap.
} else {
cleanNodeList(node.childNodes, doc, schema, inline); // For inline mode, insert a line break when unwrapping nodes that
// are not phrasing content.
if (inline && !(0, _phrasingContent.isPhrasingContent)(node) && node.nextElementSibling) {
insertAfter(doc.createElement('br'), node);
}
unwrap(node);
}
});
}
/**
* Recursively checks if an element is empty. An element is not empty if it
* contains text or contains elements with attributes such as images.
*
* @param {Element} element The element to check.
*
* @return {boolean} Wether or not the element is empty.
*/
function isEmpty(element) {
if (!element.hasChildNodes()) {
return true;
}
return Array.from(element.childNodes).every(function (node) {
if (node.nodeType === TEXT_NODE) {
return !node.nodeValue.trim();
}
if (node.nodeType === ELEMENT_NODE) {
if (node.nodeName === 'BR') {
return true;
} else if (node.hasAttributes()) {
return false;
}
return isEmpty(node);
}
return true;
});
}
/**
* Given a schema, unwraps or removes nodes, attributes and classes on HTML.
*
* @param {string} HTML The HTML to clean up.
* @param {Object} schema Schema for the HTML.
* @param {Object} inline Whether to clean for inline mode.
*
* @return {string} The cleaned up HTML.
*/
function removeInvalidHTML(HTML, schema, inline) {
var doc = document.implementation.createHTMLDocument('');
doc.body.innerHTML = HTML;
cleanNodeList(doc.body.childNodes, doc, schema, inline);
return doc.body.innerHTML;
}
//# sourceMappingURL=dom.js.map

@@ -30,2 +30,15 @@ "use strict";

var _phrasingContent = require("./phrasing-content");
Object.keys(_phrasingContent).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function get() {
return _phrasingContent[key];
}
});
});
/**

@@ -32,0 +45,0 @@ * Internal dependencies

6

package.json
{
"name": "@wordpress/dom",
"version": "2.13.1",
"version": "2.14.0",
"description": "DOM utilities module for WordPress.",

@@ -28,3 +28,3 @@ "author": "The WordPress Contributors",

"@babel/runtime": "^7.9.2",
"lodash": "^4.17.15"
"lodash": "^4.17.19"
},

@@ -34,3 +34,3 @@ "publishConfig": {

},
"gitHead": "862bb53a4af8aa5852a22445b5d12591d5500e52"
"gitHead": "07baf5a12007d31bbd4ee22113b07952f7eacc26"
}

@@ -79,2 +79,18 @@ # DOM

<a name="getPhrasingContentSchema" href="#getPhrasingContentSchema">#</a> **getPhrasingContentSchema**
Get schema of possible paths for phrasing content.
_Related_
- <https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Phrasing_content>
_Parameters_
- _context_ `string`: Set to "paste" to exclude invisible elements and sensitive data.
_Returns_
- `Object`: Schema.
<a name="getRectangleFromRange" href="#getRectangleFromRange">#</a> **getRectangleFromRange**

@@ -118,2 +134,15 @@

<a name="isEmpty" href="#isEmpty">#</a> **isEmpty**
Recursively checks if an element is empty. An element is not empty if it
contains text or contains elements with attributes such as images.
_Parameters_
- _element_ `Element`: The element to check.
_Returns_
- `boolean`: Wether or not the element is empty.
<a name="isEntirelySelected" href="#isEntirelySelected">#</a> **isEntirelySelected**

@@ -158,2 +187,22 @@

<a name="isPhrasingContent" href="#isPhrasingContent">#</a> **isPhrasingContent**
Find out whether or not the given node is phrasing content.
_Related_
- <https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Phrasing_content>
_Parameters_
- _node_ `Element`: The node to test.
_Returns_
- `boolean`: True if phrasing content, false if not.
<a name="isTextContent" href="#isTextContent">#</a> **isTextContent**
Undocumented declaration.
<a name="isTextField" href="#isTextField">#</a> **isTextField**

@@ -219,2 +268,16 @@

<a name="removeInvalidHTML" href="#removeInvalidHTML">#</a> **removeInvalidHTML**
Given a schema, unwraps or removes nodes, attributes and classes on HTML.
_Parameters_
- _HTML_ `string`: The HTML to clean up.
- _schema_ `Object`: Schema for the HTML.
- _inline_ `Object`: Whether to clean for inline mode.
_Returns_
- `string`: The cleaned up HTML.
<a name="replace" href="#replace">#</a> **replace**

@@ -221,0 +284,0 @@

/**
* External dependencies
*/
import { includes } from 'lodash';
import { includes, noop } from 'lodash';
/**
* Internal dependencies
*/
import { isPhrasingContent } from './phrasing-content';
/**
* Browser dependencies

@@ -766,1 +771,205 @@ */

}
/**
* Given a schema, unwraps or removes nodes, attributes and classes on a node
* list.
*
* @param {NodeList} nodeList The nodeList to filter.
* @param {Document} doc The document of the nodeList.
* @param {Object} schema An array of functions that can mutate with the provided node.
* @param {Object} inline Whether to clean for inline mode.
*/
function cleanNodeList( nodeList, doc, schema, inline ) {
Array.from( nodeList ).forEach( ( node ) => {
const tag = node.nodeName.toLowerCase();
// It's a valid child, if the tag exists in the schema without an isMatch
// function, or with an isMatch function that matches the node.
if (
schema.hasOwnProperty( tag ) &&
( ! schema[ tag ].isMatch || schema[ tag ].isMatch( node ) )
) {
if ( node.nodeType === ELEMENT_NODE ) {
const {
attributes = [],
classes = [],
children,
require = [],
allowEmpty,
} = schema[ tag ];
// If the node is empty and it's supposed to have children,
// remove the node.
if ( children && ! allowEmpty && isEmpty( node ) ) {
remove( node );
return;
}
if ( node.hasAttributes() ) {
// Strip invalid attributes.
Array.from( node.attributes ).forEach( ( { name } ) => {
if (
name !== 'class' &&
! includes( attributes, name )
) {
node.removeAttribute( name );
}
} );
// Strip invalid classes.
// In jsdom-jscore, 'node.classList' can be undefined.
// TODO: Explore patching this in jsdom-jscore.
if ( node.classList && node.classList.length ) {
const mattchers = classes.map( ( item ) => {
if ( typeof item === 'string' ) {
return ( className ) => className === item;
} else if ( item instanceof RegExp ) {
return ( className ) => item.test( className );
}
return noop;
} );
Array.from( node.classList ).forEach( ( name ) => {
if (
! mattchers.some( ( isMatch ) =>
isMatch( name )
)
) {
node.classList.remove( name );
}
} );
if ( ! node.classList.length ) {
node.removeAttribute( 'class' );
}
}
}
if ( node.hasChildNodes() ) {
// Do not filter any content.
if ( children === '*' ) {
return;
}
// Continue if the node is supposed to have children.
if ( children ) {
// If a parent requires certain children, but it does
// not have them, drop the parent and continue.
if (
require.length &&
! node.querySelector( require.join( ',' ) )
) {
cleanNodeList(
node.childNodes,
doc,
schema,
inline
);
unwrap( node );
// If the node is at the top, phrasing content, and
// contains children that are block content, unwrap
// the node because it is invalid.
} else if (
node.parentNode.nodeName === 'BODY' &&
isPhrasingContent( node )
) {
cleanNodeList(
node.childNodes,
doc,
schema,
inline
);
if (
Array.from( node.childNodes ).some(
( child ) => ! isPhrasingContent( child )
)
) {
unwrap( node );
}
} else {
cleanNodeList(
node.childNodes,
doc,
children,
inline
);
}
// Remove children if the node is not supposed to have any.
} else {
while ( node.firstChild ) {
remove( node.firstChild );
}
}
}
}
// Invalid child. Continue with schema at the same place and unwrap.
} else {
cleanNodeList( node.childNodes, doc, schema, inline );
// For inline mode, insert a line break when unwrapping nodes that
// are not phrasing content.
if (
inline &&
! isPhrasingContent( node ) &&
node.nextElementSibling
) {
insertAfter( doc.createElement( 'br' ), node );
}
unwrap( node );
}
} );
}
/**
* Recursively checks if an element is empty. An element is not empty if it
* contains text or contains elements with attributes such as images.
*
* @param {Element} element The element to check.
*
* @return {boolean} Wether or not the element is empty.
*/
export function isEmpty( element ) {
if ( ! element.hasChildNodes() ) {
return true;
}
return Array.from( element.childNodes ).every( ( node ) => {
if ( node.nodeType === TEXT_NODE ) {
return ! node.nodeValue.trim();
}
if ( node.nodeType === ELEMENT_NODE ) {
if ( node.nodeName === 'BR' ) {
return true;
} else if ( node.hasAttributes() ) {
return false;
}
return isEmpty( node );
}
return true;
} );
}
/**
* Given a schema, unwraps or removes nodes, attributes and classes on HTML.
*
* @param {string} HTML The HTML to clean up.
* @param {Object} schema Schema for the HTML.
* @param {Object} inline Whether to clean for inline mode.
*
* @return {string} The cleaned up HTML.
*/
export function removeInvalidHTML( HTML, schema, inline ) {
const doc = document.implementation.createHTMLDocument( '' );
doc.body.innerHTML = HTML;
cleanNodeList( doc.body.childNodes, doc, schema, inline );
return doc.body.innerHTML;
}

@@ -14,1 +14,2 @@ /**

export * from './dom';
export * from './phrasing-content';

@@ -10,4 +10,8 @@ /**

isNumberInput,
removeInvalidHTML,
isEmpty,
} from '../dom';
import { getPhrasingContentSchema } from '../phrasing-content';
describe( 'DOM', () => {

@@ -201,1 +205,152 @@ let parent;

} );
describe( 'removeInvalidHTML', () => {
const phrasingContentSchema = getPhrasingContentSchema();
const schema = {
p: {
children: phrasingContentSchema,
},
figure: {
require: [ 'img' ],
children: {
img: {
attributes: [ 'src', 'alt' ],
classes: [ 'alignleft' ],
},
figcaption: {
children: phrasingContentSchema,
},
},
},
...phrasingContentSchema,
};
it( 'should leave plain text alone', () => {
const input = 'test';
expect( removeInvalidHTML( input, schema ) ).toBe( input );
} );
it( 'should leave valid phrasing content alone', () => {
const input = '<strong>test</strong>';
expect( removeInvalidHTML( input, schema ) ).toBe( input );
} );
it( 'should remove unrecognised tags from phrasing content', () => {
const input = '<strong><div>test</div></strong>';
const output = '<strong>test</strong>';
expect( removeInvalidHTML( input, schema ) ).toBe( output );
} );
it( 'should remove unwanted whitespace outside phrasing content', () => {
const input = '<figure><img src=""> </figure>';
const output = '<figure><img src=""></figure>';
expect( removeInvalidHTML( input, schema ) ).toBe( output );
} );
it( 'should remove attributes', () => {
const input = '<p class="test">test</p>';
const output = '<p>test</p>';
expect( removeInvalidHTML( input, schema ) ).toBe( output );
} );
it( 'should remove id attributes', () => {
const input = '<p id="foo">test</p>';
const output = '<p>test</p>';
expect( removeInvalidHTML( input, schema ) ).toBe( output );
} );
it( 'should remove multiple attributes', () => {
const input = '<p class="test" id="test">test</p>';
const output = '<p>test</p>';
expect( removeInvalidHTML( input, schema ) ).toBe( output );
} );
it( 'should deep remove attributes', () => {
const input = '<p class="test">test <em id="test">test</em></p>';
const output = '<p>test <em>test</em></p>';
expect( removeInvalidHTML( input, schema ) ).toBe( output );
} );
it( 'should remove data-* attributes', () => {
const input = '<p data-reactid="1">test</p>';
const output = '<p>test</p>';
expect( removeInvalidHTML( input, schema ) ).toBe( output );
} );
it( 'should keep some attributes', () => {
const input = '<a href="#keep" target="_blank">test</a>';
const output = '<a href="#keep" target="_blank">test</a>';
expect( removeInvalidHTML( input, schema ) ).toBe( output );
} );
it( 'should keep some classes', () => {
const input = '<figure><img class="alignleft test" src=""></figure>';
const output = '<figure><img class="alignleft" src=""></figure>';
expect( removeInvalidHTML( input, schema ) ).toBe( output );
} );
it( 'should remove empty nodes that should have children', () => {
const input = '<figure> </figure>';
const output = '';
expect( removeInvalidHTML( input, schema ) ).toBe( output );
} );
it( 'should break up block content with phrasing schema', () => {
const input = '<p>test</p><p>test</p>';
const output = 'test<br>test';
expect( removeInvalidHTML( input, phrasingContentSchema, true ) ).toBe(
output
);
} );
it( 'should unwrap node that does not satisfy require', () => {
const input =
'<figure><p>test</p><figcaption>test</figcaption></figure>';
const output = '<p>test</p>test';
expect( removeInvalidHTML( input, schema ) ).toBe( output );
} );
it( 'should remove invalid phrasing content', () => {
const input = '<strong><p>test</p></strong>';
const output = '<p>test</p>';
expect( removeInvalidHTML( input, schema ) ).toEqual( output );
} );
} );
describe( 'isEmpty', () => {
function isEmptyHTML( HTML ) {
const doc = document.implementation.createHTMLDocument( '' );
doc.body.innerHTML = HTML;
return isEmpty( doc.body );
}
it( 'should return true for empty element', () => {
expect( isEmptyHTML( '' ) ).toBe( true );
} );
it( 'should return true for element with only whitespace', () => {
expect( isEmptyHTML( ' ' ) ).toBe( true );
} );
it( 'should return true for element with non breaking space', () => {
expect( isEmptyHTML( '&nbsp;' ) ).toBe( true );
} );
it( 'should return true for element with BR', () => {
expect( isEmptyHTML( '<br>' ) ).toBe( true );
} );
it( 'should return true for element with empty element', () => {
expect( isEmptyHTML( '<em></em>' ) ).toBe( true );
} );
it( 'should return false for element with image', () => {
expect( isEmptyHTML( '<img src="">' ) ).toBe( false );
} );
it( 'should return true for element with mixed empty pieces', () => {
expect( isEmptyHTML( ' <br><br><em>&nbsp; </em>' ) ).toBe( true );
} );
} );

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc