Skip to main content
Glama

mcp-appstore

by appreply-co
attributes.ts28.6 kB
/** * Methods for getting and modifying attributes. * * @module cheerio/attributes */ import { text } from '../static.js'; import { domEach, camelCase, cssCase } from '../utils.js'; import { isTag, type AnyNode, type Element } from 'domhandler'; import type { Cheerio } from '../cheerio.js'; import { innerText, textContent } from 'domutils'; const hasOwn = Object.prototype.hasOwnProperty; const rspace = /\s+/; const dataAttrPrefix = 'data-'; // Attributes that are booleans const 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 const rbrace = /^{[^]*}$|^\[[^]*]$/; /** * Gets a node's attribute. For boolean attributes, it will return the value's * name should it be set. * * Also supports getting the `value` of several form elements. * * @private * @category Attributes * @param elem - Element to get the attribute of. * @param name - Name of the attribute. * @param xmlMode - Disable handling of special HTML attributes. * @returns The attribute's value. */ function getAttr( elem: AnyNode, name: undefined, xmlMode?: boolean, ): Record<string, string> | undefined; function getAttr( elem: AnyNode, name: string, xmlMode?: boolean, ): string | undefined; function getAttr( elem: AnyNode, name: string | undefined, xmlMode?: boolean, ): Record<string, string> | string | undefined { if (!elem || !isTag(elem)) return undefined; 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 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: Element, name: string, value: string | null) { if (value === null) { removeAttribute(el, name); } else { el.attribs[name] = `${value}`; } } /** * Method for getting attributes. Gets the attribute value for only the first * element in the matched set. * * @category Attributes * @example * * ```js * $('ul').attr('id'); * //=> fruits * ``` * * @param name - Name of the attribute. * @returns The attribute's value. * @see {@link https://api.jquery.com/attr/} */ export function attr<T extends AnyNode>( this: Cheerio<T>, name: string, ): string | undefined; /** * Method for getting all attributes and their values of the first element in * the matched set. * * @category Attributes * @example * * ```js * $('ul').attr(); * //=> { id: 'fruits' } * ``` * * @returns The attribute's values. * @see {@link https://api.jquery.com/attr/} */ export function attr<T extends AnyNode>( this: Cheerio<T>, ): Record<string, string> | undefined; /** * Method for setting attributes. Sets the attribute value for only the first * element in the matched set. If you set an attribute's value to `null`, you * remove that attribute. You may also pass a `map` and `function`. * * @category Attributes * @example * * ```js * $('.apple').attr('id', 'favorite').html(); * //=> <li class="apple" id="favorite">Apple</li> * ``` * * @param name - Name of the attribute. * @param value - The new value of the attribute. * @returns The instance itself. * @see {@link https://api.jquery.com/attr/} */ export function attr<T extends AnyNode>( this: Cheerio<T>, name: string, value?: | string | null | ((this: Element, i: number, attrib: string) => string | null), ): Cheerio<T>; /** * Method for setting multiple attributes at once. Sets the attribute value for * only the first element in the matched set. If you set an attribute's value to * `null`, you remove that attribute. * * @category Attributes * @example * * ```js * $('.apple').attr({ id: 'favorite' }).html(); * //=> <li class="apple" id="favorite">Apple</li> * ``` * * @param values - Map of attribute names and values. * @returns The instance itself. * @see {@link https://api.jquery.com/attr/} */ export function attr<T extends AnyNode>( this: Cheerio<T>, values: Record<string, string | null>, ): Cheerio<T>; export function attr<T extends AnyNode>( this: Cheerio<T>, name?: string | Record<string, string | null>, value?: | string | null | ((this: Element, i: number, attrib: string) => string | null), ): string | Cheerio<T> | undefined | Record<string, string> { // 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 domEach(this, (el, i) => { if (isTag(el)) setAttr(el, name, value.call(el, i, el.attribs[name])); }); } return domEach(this, (el) => { if (!isTag(el)) return; if (typeof name === 'object') { for (const objName of Object.keys(name)) { const objValue = name[objName]; setAttr(el, objName, objValue); } } else { setAttr(el, name as string, value as string); } }); } return arguments.length > 1 ? this : getAttr(this[0], name as string, this.options.xmlMode); } /** * Gets a node's prop. * * @private * @category Attributes * @param el - Element to get the prop of. * @param name - Name of the prop. * @param xmlMode - Disable handling of special HTML attributes. * @returns The prop's value. */ function getProp( el: Element, name: string, xmlMode?: boolean, ): string | undefined | Element[keyof Element] { 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. * @param xmlMode - Disable handling of special HTML attributes. */ function setProp(el: Element, name: string, value: unknown, xmlMode?: boolean) { if (name in el) { // @ts-expect-error Overriding value el[name] = value; } else { setAttr( el, name, !xmlMode && rboolean.test(name) ? (value ? '' : null) : `${value}`, ); } } interface StyleProp { length: number; [key: string]: string | number; [index: number]: string; } /** * Method for getting and setting properties. Gets the property value for only * the first element in the matched set. * * @category Attributes * @example * * ```js * $('input[type="checkbox"]').prop('checked'); * //=> false * * $('input[type="checkbox"]').prop('checked', true).val(); * //=> ok * ``` * * @param name - Name of the property. * @returns If `value` is specified the instance itself, otherwise the prop's * value. * @see {@link https://api.jquery.com/prop/} */ export function prop<T extends AnyNode>( this: Cheerio<T>, name: 'tagName' | 'nodeName', ): string | undefined; export function prop<T extends AnyNode>( this: Cheerio<T>, name: 'innerHTML' | 'outerHTML' | 'innerText' | 'textContent', ): string | null; /** * Get a parsed CSS style object. * * @param name - Name of the property. * @returns The style object, or `undefined` if the element has no `style` * attribute. */ export function prop<T extends AnyNode>( this: Cheerio<T>, name: 'style', ): StyleProp | undefined; /** * Resolve `href` or `src` of supported elements. Requires the `baseURI` option * to be set, and a global `URL` object to be part of the environment. * * @example With `baseURI` set to `'https://example.com'`: * * ```js * $('<img src="image.png">').prop('src'); * //=> 'https://example.com/image.png' * ``` * * @param name - Name of the property. * @returns The resolved URL, or `undefined` if the element is not supported. */ export function prop<T extends AnyNode>( this: Cheerio<T>, name: 'href' | 'src', ): string | undefined; /** * Get a property of an element. * * @param name - Name of the property. * @returns The property's value. */ export function prop<T extends AnyNode, K extends keyof Element>( this: Cheerio<T>, name: K, ): Element[K]; /** * Set a property of an element. * * @param name - Name of the property. * @param value - Value to set the property to. * @returns The instance itself. */ export function prop<T extends AnyNode, K extends keyof Element>( this: Cheerio<T>, name: K, value: | Element[K] | ((this: Element, i: number, prop: K) => Element[keyof Element]), ): Cheerio<T>; /** * Set multiple properties of an element. * * @example * * ```js * $('input[type="checkbox"]').prop({ * checked: true, * disabled: false, * }); * ``` * * @param map - Object of properties to set. * @returns The instance itself. */ export function prop<T extends AnyNode>( this: Cheerio<T>, map: Record<string, string | Element[keyof Element] | boolean>, ): Cheerio<T>; /** * Set a property of an element. * * @param name - Name of the property. * @param value - Value to set the property to. * @returns The instance itself. */ export function prop<T extends AnyNode>( this: Cheerio<T>, name: string, value: | string | boolean | null | ((this: Element, i: number, prop: string) => string | boolean), ): Cheerio<T>; /** * Get a property of an element. * * @param name - The property's name. * @returns The property's value. */ export function prop<T extends AnyNode>(this: Cheerio<T>, name: string): string; export function prop<T extends AnyNode>( this: Cheerio<T>, name: string | Record<string, string | Element[keyof Element] | boolean>, value?: | (( this: Element, i: number, prop: string | undefined, ) => string | Element[keyof Element] | boolean) | unknown, ): Cheerio<T> | string | undefined | null | Element[keyof Element] | StyleProp { if (typeof name === 'string' && value === undefined) { const el = this[0]; if (!el || !isTag(el)) return undefined; switch (name) { case 'style': { const property = this.css() as StyleProp; const keys = Object.keys(property); for (let i = 0; i < keys.length; i++) { property[i] = keys[i]; } property.length = keys.length; return property; } case 'tagName': case 'nodeName': { return el.name.toUpperCase(); } case 'href': case 'src': { const prop = el.attribs?.[name]; if ( typeof URL !== 'undefined' && ((name === 'href' && (el.tagName === 'a' || el.tagName === 'link')) || (name === 'src' && (el.tagName === 'img' || el.tagName === 'iframe' || el.tagName === 'audio' || el.tagName === 'video' || el.tagName === 'source'))) && prop !== undefined && this.options.baseURI ) { return new URL(prop, this.options.baseURI).href; } return prop; } case 'innerText': { return innerText(el); } case 'textContent': { return 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 TypeError('Bad combination of arguments.'); } return domEach(this, (el, i) => { if (isTag(el)) { setProp( el, name, value.call(el, i, getProp(el, name, this.options.xmlMode)), this.options.xmlMode, ); } }); } return domEach(this, (el) => { if (!isTag(el)) return; if (typeof name === 'object') { for (const key of Object.keys(name)) { const val = name[key]; setProp(el, key, val, this.options.xmlMode); } } else { setProp(el, name, value, this.options.xmlMode); } }); } return undefined; } /** * An element with a data attribute. * * @private */ interface DataElement extends Element { /** The data attribute. */ data?: Record<string, unknown>; } /** * Sets the value of a data attribute. * * @private * @param elem - The element to set the data attribute on. * @param name - The data attribute's name. * @param value - The data attribute's value. */ function setData( elem: DataElement, name: string | Record<string, unknown>, value?: unknown, ) { elem.data ??= {}; if (typeof name === 'object') Object.assign(elem.data, name); else if (typeof name === 'string' && value !== undefined) { elem.data[name] = value; } } /** * Read _all_ HTML5 `data-*` attributes from the equivalent HTML5 `data-*` * attribute, and cache the value in the node's internal data store. * * @private * @category Attributes * @param el - Element to get the data attribute of. * @returns A map with all of the data attributes. */ function readAllData(el: DataElement): unknown { for (const domName of Object.keys(el.attribs)) { if (!domName.startsWith(dataAttrPrefix)) { continue; } const jsName = camelCase(domName.slice(dataAttrPrefix.length)); if (!hasOwn.call(el.data, jsName)) { el.data![jsName] = parseDataValue(el.attribs[domName]); } } return el.data; } /** * Read the specified attribute from the equivalent HTML5 `data-*` attribute, * and (if present) cache the value in the node's internal data store. * * @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. */ function readData(el: DataElement, name: string): unknown { const domName = dataAttrPrefix + cssCase(name); const data = el.data!; if (hasOwn.call(data, name)) { return data[name]; } if (hasOwn.call(el.attribs, domName)) { return (data[name] = parseDataValue(el.attribs[domName])); } return undefined; } /** * Coerce string data-* attributes to their corresponding JavaScript primitives. * * @private * @category Attributes * @param value - The value to parse. * @returns The parsed value. */ function parseDataValue(value: string): unknown { if (value === 'null') return null; if (value === 'true') return true; if (value === 'false') return false; const num = Number(value); if (value === String(num)) return num; if (rbrace.test(value)) { try { return JSON.parse(value); } catch { /* Ignore */ } } return value; } /** * Method for getting data attributes, for only the first element in the matched * set. * * @category Attributes * @example * * ```js * $('<div data-apple-color="red"></div>').data('apple-color'); * //=> 'red' * ``` * * @param name - Name of the data attribute. * @returns The data attribute's value, or `undefined` if the attribute does not * exist. * @see {@link https://api.jquery.com/data/} */ export function data<T extends AnyNode>( this: Cheerio<T>, name: string, ): unknown | undefined; /** * Method for getting all of an element's data attributes, for only the first * element in the matched set. * * @category Attributes * @example * * ```js * $('<div data-apple-color="red"></div>').data(); * //=> { appleColor: 'red' } * ``` * * @returns A map with all of the data attributes. * @see {@link https://api.jquery.com/data/} */ export function data<T extends AnyNode>( this: Cheerio<T>, ): Record<string, unknown>; /** * Method for setting data attributes, for only the first element in the matched * set. * * @category Attributes * @example * * ```js * const apple = $('.apple').data('kind', 'mac'); * * apple.data('kind'); * //=> 'mac' * ``` * * @param name - Name of the data attribute. * @param value - The new value. * @returns The instance itself. * @see {@link https://api.jquery.com/data/} */ export function data<T extends AnyNode>( this: Cheerio<T>, name: string, value: unknown, ): Cheerio<T>; /** * Method for setting multiple data attributes at once, for only the first * element in the matched set. * * @category Attributes * @example * * ```js * const apple = $('.apple').data({ kind: 'mac' }); * * apple.data('kind'); * //=> 'mac' * ``` * * @param values - Map of names to values. * @returns The instance itself. * @see {@link https://api.jquery.com/data/} */ export function data<T extends AnyNode>( this: Cheerio<T>, values: Record<string, unknown>, ): Cheerio<T>; export function data<T extends AnyNode>( this: Cheerio<T>, name?: string | Record<string, unknown>, value?: unknown, ): unknown | Cheerio<T> | undefined | Record<string, unknown> { const elem = this[0]; if (!elem || !isTag(elem)) return; const dataEl: DataElement = elem; dataEl.data ??= {}; // Return the entire data object if no data specified if (name == null) { return readAllData(dataEl); } // Set the value (with attr map support) if (typeof name === 'object' || value !== undefined) { domEach(this, (el) => { if (isTag(el)) { if (typeof name === 'object') setData(el, name); else setData(el, name, value as unknown); } }); return this; } return readData(dataEl, name); } /** * Method for getting the value of input, select, and textarea. Note: Support * for `map`, and `function` has not been added yet. * * @category Attributes * @example * * ```js * $('input[type="text"]').val(); * //=> input_text * ``` * * @returns The value. * @see {@link https://api.jquery.com/val/} */ export function val<T extends AnyNode>( this: Cheerio<T>, ): string | undefined | string[]; /** * Method for setting the value of input, select, and textarea. Note: Support * for `map`, and `function` has not been added yet. * * @category Attributes * @example * * ```js * $('input[type="text"]').val('test').html(); * //=> <input type="text" value="test"/> * ``` * * @param value - The new value. * @returns The instance itself. * @see {@link https://api.jquery.com/val/} */ export function val<T extends AnyNode>( this: Cheerio<T>, value: string | string[], ): Cheerio<T>; export function val<T extends AnyNode>( this: Cheerio<T>, value?: string | string[], ): string | string[] | Cheerio<T> | undefined { const querying = arguments.length === 0; const element = this[0]; if (!element || !isTag(element)) return querying ? undefined : this; switch (element.name) { case 'textarea': { return this.text(value as string); } case 'select': { const option = this.find('option:selected'); if (!querying) { if (this.attr('multiple') == null && typeof value === 'object') { return this; } this.find('option').removeAttr('selected'); const values = typeof value === 'object' ? value : [value]; for (const val of values) { this.find(`option[value="${val}"]`).attr('selected', ''); } return this; } return this.attr('multiple') ? option.toArray().map((el) => text(el.children)) : option.attr('value'); } case 'input': case 'option': { return querying ? this.attr('value') : this.attr('value', value as string); } } return undefined; } /** * Remove an attribute. * * @private * @param elem - Node to remove attribute from. * @param name - Name of the attribute to remove. */ function removeAttribute(elem: Element, name: string) { 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?: string): string[] { 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/} */ export function removeAttr<T extends AnyNode>( this: Cheerio<T>, name: string, ): Cheerio<T> { const attrNames = splitNames(name); for (const attrName of attrNames) { domEach(this, (elem) => { if (isTag(elem)) removeAttribute(elem, attrName); }); } return this; } /** * 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/} */ export function hasClass<T extends AnyNode>( this: Cheerio<T>, className: string, ): boolean { return this.toArray().some((elem) => { const clazz = isTag(elem) && elem.attribs['class']; let idx = -1; if (clazz && className.length > 0) { while ((idx = clazz.indexOf(className, idx + 1)) > -1) { const end = idx + className.length; if ( (idx === 0 || rspace.test(clazz[idx - 1])) && (end === clazz.length || rspace.test(clazz[end])) ) { return true; } } } return false; }); } /** * 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/} */ export function addClass<T extends AnyNode, R extends ArrayLike<T>>( this: R, value?: | string | ((this: Element, i: number, className: string) => string | undefined), ): R { // Support functions if (typeof value === 'function') { return domEach(this, (el, i) => { if (isTag(el)) { const 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; const classNames = value.split(rspace); const numElements = this.length; for (let i = 0; i < numElements; i++) { const el = this[i]; // If selected element isn't a tag, move on if (!isTag(el)) continue; // If we don't already have classes — always set xmlMode to false here, as it doesn't matter for classes const className = getAttr(el, 'class', false); if (className) { let setClass = ` ${className} `; // Check if class already exists for (const cn of classNames) { const appendClass = `${cn} `; if (!setClass.includes(` ${appendClass}`)) setClass += appendClass; } setAttr(el, 'class', setClass.trim()); } else { setAttr(el, 'class', classNames.join(' ').trim()); } } return this; } /** * 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/} */ export function removeClass<T extends AnyNode, R extends ArrayLike<T>>( this: R, name?: | string | ((this: Element, i: number, className: string) => string | undefined), ): R { // Handle if value is a function if (typeof name === 'function') { return domEach(this, (el, i) => { if (isTag(el)) { removeClass.call([el], name.call(el, i, el.attribs['class'] || '')); } }); } const classes = splitNames(name); const numClasses = classes.length; const removeAll = arguments.length === 0; return domEach(this, (el) => { if (!isTag(el)) return; if (removeAll) { // Short circuit the remove all case as this is the nice one el.attribs['class'] = ''; } else { const elClasses = splitNames(el.attribs['class']); let changed = false; for (let j = 0; j < numClasses; j++) { const 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(' '); } } }); } /** * 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/} */ export function toggleClass<T extends AnyNode, R extends ArrayLike<T>>( this: R, value?: | string | (( this: Element, i: number, className: string, stateVal?: boolean, ) => string), stateVal?: boolean, ): R { // Support functions if (typeof value === 'function') { return domEach(this, (el, i) => { if (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; const classNames = value.split(rspace); const numClasses = classNames.length; const state = typeof stateVal === 'boolean' ? (stateVal ? 1 : -1) : 0; const numElements = this.length; for (let i = 0; i < numElements; i++) { const el = this[i]; // If selected element isn't a tag, move on if (!isTag(el)) continue; const elementClasses = splitNames(el.attribs['class']); // Check if class already exists for (let j = 0; j < numClasses; j++) { // Check if the class name is currently defined const 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; }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/appreply-co/mcp-appstore'

If you have feedback or need assistance with the MCP directory API, please join our Discord server