You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
622 lines
20 KiB
622 lines
20 KiB
"use strict"; |
|
/** |
|
* Methods for getting and modifying attributes. |
|
* |
|
* @module cheerio/attributes |
|
*/ |
|
Object.defineProperty(exports, "__esModule", { value: true }); |
|
exports.toggleClass = exports.removeClass = exports.addClass = exports.hasClass = exports.removeAttr = exports.val = exports.data = exports.prop = exports.attr = void 0; |
|
var static_js_1 = require("../static.js"); |
|
var utils_js_1 = require("../utils.js"); |
|
var domutils_1 = require("domutils"); |
|
var hasOwn = Object.prototype.hasOwnProperty; |
|
var rspace = /\s+/; |
|
var dataAttrPrefix = 'data-'; |
|
/* |
|
* Lookup table for coercing string data-* attributes to their corresponding |
|
* JavaScript primitives |
|
*/ |
|
var primitives = { |
|
null: null, |
|
true: true, |
|
false: false, |
|
}; |
|
// Attributes that are booleans |
|
var rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i; |
|
// Matches strings that look like JSON objects or arrays |
|
var rbrace = /^{[^]*}$|^\[[^]*]$/; |
|
function getAttr(elem, name, xmlMode) { |
|
var _a; |
|
if (!elem || !(0, utils_js_1.isTag)(elem)) |
|
return undefined; |
|
(_a = elem.attribs) !== null && _a !== void 0 ? _a : (elem.attribs = {}); |
|
// Return the entire attribs object if no attribute specified |
|
if (!name) { |
|
return elem.attribs; |
|
} |
|
if (hasOwn.call(elem.attribs, name)) { |
|
// Get the (decoded) attribute |
|
return !xmlMode && rboolean.test(name) ? name : elem.attribs[name]; |
|
} |
|
// Mimic the DOM and return text content as value for `option's` |
|
if (elem.name === 'option' && name === 'value') { |
|
return (0, static_js_1.text)(elem.children); |
|
} |
|
// Mimic DOM with default value for radios/checkboxes |
|
if (elem.name === 'input' && |
|
(elem.attribs['type'] === 'radio' || elem.attribs['type'] === 'checkbox') && |
|
name === 'value') { |
|
return 'on'; |
|
} |
|
return undefined; |
|
} |
|
/** |
|
* Sets the value of an attribute. The attribute will be deleted if the value is `null`. |
|
* |
|
* @private |
|
* @param el - The element to set the attribute on. |
|
* @param name - The attribute's name. |
|
* @param value - The attribute's value. |
|
*/ |
|
function setAttr(el, name, value) { |
|
if (value === null) { |
|
removeAttribute(el, name); |
|
} |
|
else { |
|
el.attribs[name] = "".concat(value); |
|
} |
|
} |
|
function attr(name, value) { |
|
// Set the value (with attr map support) |
|
if (typeof name === 'object' || value !== undefined) { |
|
if (typeof value === 'function') { |
|
if (typeof name !== 'string') { |
|
{ |
|
throw new Error('Bad combination of arguments.'); |
|
} |
|
} |
|
return (0, utils_js_1.domEach)(this, function (el, i) { |
|
if ((0, utils_js_1.isTag)(el)) |
|
setAttr(el, name, value.call(el, i, el.attribs[name])); |
|
}); |
|
} |
|
return (0, utils_js_1.domEach)(this, function (el) { |
|
if (!(0, utils_js_1.isTag)(el)) |
|
return; |
|
if (typeof name === 'object') { |
|
Object.keys(name).forEach(function (objName) { |
|
var objValue = name[objName]; |
|
setAttr(el, objName, objValue); |
|
}); |
|
} |
|
else { |
|
setAttr(el, name, value); |
|
} |
|
}); |
|
} |
|
return arguments.length > 1 |
|
? this |
|
: getAttr(this[0], name, this.options.xmlMode); |
|
} |
|
exports.attr = attr; |
|
/** |
|
* Gets a node's prop. |
|
* |
|
* @private |
|
* @category Attributes |
|
* @param el - Element to get the prop of. |
|
* @param name - Name of the prop. |
|
* @returns The prop's value. |
|
*/ |
|
function getProp(el, name, xmlMode) { |
|
return name in el |
|
? // @ts-expect-error TS doesn't like us accessing the value directly here. |
|
el[name] |
|
: !xmlMode && rboolean.test(name) |
|
? getAttr(el, name, false) !== undefined |
|
: getAttr(el, name, xmlMode); |
|
} |
|
/** |
|
* Sets the value of a prop. |
|
* |
|
* @private |
|
* @param el - The element to set the prop on. |
|
* @param name - The prop's name. |
|
* @param value - The prop's value. |
|
*/ |
|
function setProp(el, name, value, xmlMode) { |
|
if (name in el) { |
|
// @ts-expect-error Overriding value |
|
el[name] = value; |
|
} |
|
else { |
|
setAttr(el, name, !xmlMode && rboolean.test(name) ? (value ? '' : null) : "".concat(value)); |
|
} |
|
} |
|
function prop(name, value) { |
|
var _this = this; |
|
var _a; |
|
if (typeof name === 'string' && value === undefined) { |
|
var el = this[0]; |
|
if (!el || !(0, utils_js_1.isTag)(el)) |
|
return undefined; |
|
switch (name) { |
|
case 'style': { |
|
var property_1 = this.css(); |
|
var keys = Object.keys(property_1); |
|
keys.forEach(function (p, i) { |
|
property_1[i] = p; |
|
}); |
|
property_1.length = keys.length; |
|
return property_1; |
|
} |
|
case 'tagName': |
|
case 'nodeName': { |
|
return el.name.toUpperCase(); |
|
} |
|
case 'href': |
|
case 'src': { |
|
var prop_1 = (_a = el.attribs) === null || _a === void 0 ? void 0 : _a[name]; |
|
/* eslint-disable node/no-unsupported-features/node-builtins */ |
|
if (typeof URL !== 'undefined' && |
|
((name === 'href' && (el.tagName === 'a' || el.name === 'link')) || |
|
(name === 'src' && |
|
(el.tagName === 'img' || |
|
el.tagName === 'iframe' || |
|
el.tagName === 'audio' || |
|
el.tagName === 'video' || |
|
el.tagName === 'source'))) && |
|
prop_1 !== undefined && |
|
this.options.baseURI) { |
|
return new URL(prop_1, this.options.baseURI).href; |
|
} |
|
/* eslint-enable node/no-unsupported-features/node-builtins */ |
|
return prop_1; |
|
} |
|
case 'innerText': { |
|
return (0, domutils_1.innerText)(el); |
|
} |
|
case 'textContent': { |
|
return (0, domutils_1.textContent)(el); |
|
} |
|
case 'outerHTML': |
|
return this.clone().wrap('<container />').parent().html(); |
|
case 'innerHTML': |
|
return this.html(); |
|
default: |
|
return getProp(el, name, this.options.xmlMode); |
|
} |
|
} |
|
if (typeof name === 'object' || value !== undefined) { |
|
if (typeof value === 'function') { |
|
if (typeof name === 'object') { |
|
throw new Error('Bad combination of arguments.'); |
|
} |
|
return (0, utils_js_1.domEach)(this, function (el, i) { |
|
if ((0, utils_js_1.isTag)(el)) { |
|
setProp(el, name, value.call(el, i, getProp(el, name, _this.options.xmlMode)), _this.options.xmlMode); |
|
} |
|
}); |
|
} |
|
return (0, utils_js_1.domEach)(this, function (el) { |
|
if (!(0, utils_js_1.isTag)(el)) |
|
return; |
|
if (typeof name === 'object') { |
|
Object.keys(name).forEach(function (key) { |
|
var val = name[key]; |
|
setProp(el, key, val, _this.options.xmlMode); |
|
}); |
|
} |
|
else { |
|
setProp(el, name, value, _this.options.xmlMode); |
|
} |
|
}); |
|
} |
|
return undefined; |
|
} |
|
exports.prop = prop; |
|
/** |
|
* Sets the value of a data attribute. |
|
* |
|
* @private |
|
* @param el - The element to set the data attribute on. |
|
* @param name - The data attribute's name. |
|
* @param value - The data attribute's value. |
|
*/ |
|
function setData(el, name, value) { |
|
var _a; |
|
var elem = el; |
|
(_a = elem.data) !== null && _a !== void 0 ? _a : (elem.data = {}); |
|
if (typeof name === 'object') |
|
Object.assign(elem.data, name); |
|
else if (typeof name === 'string' && value !== undefined) { |
|
elem.data[name] = value; |
|
} |
|
} |
|
/** |
|
* Read the specified attribute from the equivalent HTML5 `data-*` attribute, |
|
* and (if present) cache the value in the node's internal data store. If no |
|
* attribute name is specified, read _all_ HTML5 `data-*` attributes in this manner. |
|
* |
|
* @private |
|
* @category Attributes |
|
* @param el - Element to get the data attribute of. |
|
* @param name - Name of the data attribute. |
|
* @returns The data attribute's value, or a map with all of the data attributes. |
|
*/ |
|
function readData(el, name) { |
|
var domNames; |
|
var jsNames; |
|
var value; |
|
if (name == null) { |
|
domNames = Object.keys(el.attribs).filter(function (attrName) { |
|
return attrName.startsWith(dataAttrPrefix); |
|
}); |
|
jsNames = domNames.map(function (domName) { |
|
return (0, utils_js_1.camelCase)(domName.slice(dataAttrPrefix.length)); |
|
}); |
|
} |
|
else { |
|
domNames = [dataAttrPrefix + (0, utils_js_1.cssCase)(name)]; |
|
jsNames = [name]; |
|
} |
|
for (var idx = 0; idx < domNames.length; ++idx) { |
|
var domName = domNames[idx]; |
|
var jsName = jsNames[idx]; |
|
if (hasOwn.call(el.attribs, domName) && |
|
!hasOwn.call(el.data, jsName)) { |
|
value = el.attribs[domName]; |
|
if (hasOwn.call(primitives, value)) { |
|
value = primitives[value]; |
|
} |
|
else if (value === String(Number(value))) { |
|
value = Number(value); |
|
} |
|
else if (rbrace.test(value)) { |
|
try { |
|
value = JSON.parse(value); |
|
} |
|
catch (e) { |
|
/* Ignore */ |
|
} |
|
} |
|
el.data[jsName] = value; |
|
} |
|
} |
|
return name == null ? el.data : value; |
|
} |
|
function data(name, value) { |
|
var _a; |
|
var elem = this[0]; |
|
if (!elem || !(0, utils_js_1.isTag)(elem)) |
|
return; |
|
var dataEl = elem; |
|
(_a = dataEl.data) !== null && _a !== void 0 ? _a : (dataEl.data = {}); |
|
// Return the entire data object if no data specified |
|
if (!name) { |
|
return readData(dataEl); |
|
} |
|
// Set the value (with attr map support) |
|
if (typeof name === 'object' || value !== undefined) { |
|
(0, utils_js_1.domEach)(this, function (el) { |
|
if ((0, utils_js_1.isTag)(el)) { |
|
if (typeof name === 'object') |
|
setData(el, name); |
|
else |
|
setData(el, name, value); |
|
} |
|
}); |
|
return this; |
|
} |
|
if (hasOwn.call(dataEl.data, name)) { |
|
return dataEl.data[name]; |
|
} |
|
return readData(dataEl, name); |
|
} |
|
exports.data = data; |
|
function val(value) { |
|
var querying = arguments.length === 0; |
|
var element = this[0]; |
|
if (!element || !(0, utils_js_1.isTag)(element)) |
|
return querying ? undefined : this; |
|
switch (element.name) { |
|
case 'textarea': |
|
return this.text(value); |
|
case 'select': { |
|
var option = this.find('option:selected'); |
|
if (!querying) { |
|
if (this.attr('multiple') == null && typeof value === 'object') { |
|
return this; |
|
} |
|
this.find('option').removeAttr('selected'); |
|
var values = typeof value !== 'object' ? [value] : value; |
|
for (var i = 0; i < values.length; i++) { |
|
this.find("option[value=\"".concat(values[i], "\"]")).attr('selected', ''); |
|
} |
|
return this; |
|
} |
|
return this.attr('multiple') |
|
? option.toArray().map(function (el) { return (0, static_js_1.text)(el.children); }) |
|
: option.attr('value'); |
|
} |
|
case 'input': |
|
case 'option': |
|
return querying |
|
? this.attr('value') |
|
: this.attr('value', value); |
|
} |
|
return undefined; |
|
} |
|
exports.val = val; |
|
/** |
|
* Remove an attribute. |
|
* |
|
* @private |
|
* @param elem - Node to remove attribute from. |
|
* @param name - Name of the attribute to remove. |
|
*/ |
|
function removeAttribute(elem, name) { |
|
if (!elem.attribs || !hasOwn.call(elem.attribs, name)) |
|
return; |
|
delete elem.attribs[name]; |
|
} |
|
/** |
|
* Splits a space-separated list of names to individual names. |
|
* |
|
* @category Attributes |
|
* @param names - Names to split. |
|
* @returns - Split names. |
|
*/ |
|
function splitNames(names) { |
|
return names ? names.trim().split(rspace) : []; |
|
} |
|
/** |
|
* Method for removing attributes by `name`. |
|
* |
|
* @category Attributes |
|
* @example |
|
* |
|
* ```js |
|
* $('.pear').removeAttr('class').html(); |
|
* //=> <li>Pear</li> |
|
* |
|
* $('.apple').attr('id', 'favorite'); |
|
* $('.apple').removeAttr('id class').html(); |
|
* //=> <li>Apple</li> |
|
* ``` |
|
* |
|
* @param name - Name of the attribute. |
|
* @returns The instance itself. |
|
* @see {@link https://api.jquery.com/removeAttr/} |
|
*/ |
|
function removeAttr(name) { |
|
var attrNames = splitNames(name); |
|
var _loop_1 = function (i) { |
|
(0, utils_js_1.domEach)(this_1, function (elem) { |
|
if ((0, utils_js_1.isTag)(elem)) |
|
removeAttribute(elem, attrNames[i]); |
|
}); |
|
}; |
|
var this_1 = this; |
|
for (var i = 0; i < attrNames.length; i++) { |
|
_loop_1(i); |
|
} |
|
return this; |
|
} |
|
exports.removeAttr = removeAttr; |
|
/** |
|
* Check to see if _any_ of the matched elements have the given `className`. |
|
* |
|
* @category Attributes |
|
* @example |
|
* |
|
* ```js |
|
* $('.pear').hasClass('pear'); |
|
* //=> true |
|
* |
|
* $('apple').hasClass('fruit'); |
|
* //=> false |
|
* |
|
* $('li').hasClass('pear'); |
|
* //=> true |
|
* ``` |
|
* |
|
* @param className - Name of the class. |
|
* @returns Indicates if an element has the given `className`. |
|
* @see {@link https://api.jquery.com/hasClass/} |
|
*/ |
|
function hasClass(className) { |
|
return this.toArray().some(function (elem) { |
|
var clazz = (0, utils_js_1.isTag)(elem) && elem.attribs['class']; |
|
var idx = -1; |
|
if (clazz && className.length) { |
|
while ((idx = clazz.indexOf(className, idx + 1)) > -1) { |
|
var end = idx + className.length; |
|
if ((idx === 0 || rspace.test(clazz[idx - 1])) && |
|
(end === clazz.length || rspace.test(clazz[end]))) { |
|
return true; |
|
} |
|
} |
|
} |
|
return false; |
|
}); |
|
} |
|
exports.hasClass = hasClass; |
|
/** |
|
* Adds class(es) to all of the matched elements. Also accepts a `function`. |
|
* |
|
* @category Attributes |
|
* @example |
|
* |
|
* ```js |
|
* $('.pear').addClass('fruit').html(); |
|
* //=> <li class="pear fruit">Pear</li> |
|
* |
|
* $('.apple').addClass('fruit red').html(); |
|
* //=> <li class="apple fruit red">Apple</li> |
|
* ``` |
|
* |
|
* @param value - Name of new class. |
|
* @returns The instance itself. |
|
* @see {@link https://api.jquery.com/addClass/} |
|
*/ |
|
function addClass(value) { |
|
// Support functions |
|
if (typeof value === 'function') { |
|
return (0, utils_js_1.domEach)(this, function (el, i) { |
|
if ((0, utils_js_1.isTag)(el)) { |
|
var className = el.attribs['class'] || ''; |
|
addClass.call([el], value.call(el, i, className)); |
|
} |
|
}); |
|
} |
|
// Return if no value or not a string or function |
|
if (!value || typeof value !== 'string') |
|
return this; |
|
var classNames = value.split(rspace); |
|
var numElements = this.length; |
|
for (var i = 0; i < numElements; i++) { |
|
var el = this[i]; |
|
// If selected element isn't a tag, move on |
|
if (!(0, utils_js_1.isTag)(el)) |
|
continue; |
|
// If we don't already have classes — always set xmlMode to false here, as it doesn't matter for classes |
|
var className = getAttr(el, 'class', false); |
|
if (!className) { |
|
setAttr(el, 'class', classNames.join(' ').trim()); |
|
} |
|
else { |
|
var setClass = " ".concat(className, " "); |
|
// Check if class already exists |
|
for (var j = 0; j < classNames.length; j++) { |
|
var appendClass = "".concat(classNames[j], " "); |
|
if (!setClass.includes(" ".concat(appendClass))) |
|
setClass += appendClass; |
|
} |
|
setAttr(el, 'class', setClass.trim()); |
|
} |
|
} |
|
return this; |
|
} |
|
exports.addClass = addClass; |
|
/** |
|
* Removes one or more space-separated classes from the selected elements. If no |
|
* `className` is defined, all classes will be removed. Also accepts a `function`. |
|
* |
|
* @category Attributes |
|
* @example |
|
* |
|
* ```js |
|
* $('.pear').removeClass('pear').html(); |
|
* //=> <li class="">Pear</li> |
|
* |
|
* $('.apple').addClass('red').removeClass().html(); |
|
* //=> <li class="">Apple</li> |
|
* ``` |
|
* |
|
* @param name - Name of the class. If not specified, removes all elements. |
|
* @returns The instance itself. |
|
* @see {@link https://api.jquery.com/removeClass/} |
|
*/ |
|
function removeClass(name) { |
|
// Handle if value is a function |
|
if (typeof name === 'function') { |
|
return (0, utils_js_1.domEach)(this, function (el, i) { |
|
if ((0, utils_js_1.isTag)(el)) { |
|
removeClass.call([el], name.call(el, i, el.attribs['class'] || '')); |
|
} |
|
}); |
|
} |
|
var classes = splitNames(name); |
|
var numClasses = classes.length; |
|
var removeAll = arguments.length === 0; |
|
return (0, utils_js_1.domEach)(this, function (el) { |
|
if (!(0, utils_js_1.isTag)(el)) |
|
return; |
|
if (removeAll) { |
|
// Short circuit the remove all case as this is the nice one |
|
el.attribs['class'] = ''; |
|
} |
|
else { |
|
var elClasses = splitNames(el.attribs['class']); |
|
var changed = false; |
|
for (var j = 0; j < numClasses; j++) { |
|
var index = elClasses.indexOf(classes[j]); |
|
if (index >= 0) { |
|
elClasses.splice(index, 1); |
|
changed = true; |
|
/* |
|
* We have to do another pass to ensure that there are not duplicate |
|
* classes listed |
|
*/ |
|
j--; |
|
} |
|
} |
|
if (changed) { |
|
el.attribs['class'] = elClasses.join(' '); |
|
} |
|
} |
|
}); |
|
} |
|
exports.removeClass = removeClass; |
|
/** |
|
* Add or remove class(es) from the matched elements, depending on either the |
|
* class's presence or the value of the switch argument. Also accepts a `function`. |
|
* |
|
* @category Attributes |
|
* @example |
|
* |
|
* ```js |
|
* $('.apple.green').toggleClass('fruit green red').html(); |
|
* //=> <li class="apple fruit red">Apple</li> |
|
* |
|
* $('.apple.green').toggleClass('fruit green red', true).html(); |
|
* //=> <li class="apple green fruit red">Apple</li> |
|
* ``` |
|
* |
|
* @param value - Name of the class. Can also be a function. |
|
* @param stateVal - If specified the state of the class. |
|
* @returns The instance itself. |
|
* @see {@link https://api.jquery.com/toggleClass/} |
|
*/ |
|
function toggleClass(value, stateVal) { |
|
// Support functions |
|
if (typeof value === 'function') { |
|
return (0, utils_js_1.domEach)(this, function (el, i) { |
|
if ((0, utils_js_1.isTag)(el)) { |
|
toggleClass.call([el], value.call(el, i, el.attribs['class'] || '', stateVal), stateVal); |
|
} |
|
}); |
|
} |
|
// Return if no value or not a string or function |
|
if (!value || typeof value !== 'string') |
|
return this; |
|
var classNames = value.split(rspace); |
|
var numClasses = classNames.length; |
|
var state = typeof stateVal === 'boolean' ? (stateVal ? 1 : -1) : 0; |
|
var numElements = this.length; |
|
for (var i = 0; i < numElements; i++) { |
|
var el = this[i]; |
|
// If selected element isn't a tag, move on |
|
if (!(0, utils_js_1.isTag)(el)) |
|
continue; |
|
var elementClasses = splitNames(el.attribs['class']); |
|
// Check if class already exists |
|
for (var j = 0; j < numClasses; j++) { |
|
// Check if the class name is currently defined |
|
var index = elementClasses.indexOf(classNames[j]); |
|
// Add if stateValue === true or we are toggling and there is no value |
|
if (state >= 0 && index < 0) { |
|
elementClasses.push(classNames[j]); |
|
} |
|
else if (state <= 0 && index >= 0) { |
|
// Otherwise remove but only if the item exists |
|
elementClasses.splice(index, 1); |
|
} |
|
} |
|
el.attribs['class'] = elementClasses.join(' '); |
|
} |
|
return this; |
|
} |
|
exports.toggleClass = toggleClass; |
|
//# sourceMappingURL=attributes.js.map
|