| |
| |
| |
| |
| |
|
|
| 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-'; |
|
|
| |
| const rboolean = |
| /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i; |
| |
| const rbrace = /^{[^]*}$|^\[[^]*]$/; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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 ??= {}; |
|
|
| |
| if (!name) { |
| return elem.attribs; |
| } |
|
|
| if (hasOwn.call(elem.attribs, name)) { |
| |
| return !xmlMode && rboolean.test(name) ? name : elem.attribs[name]; |
| } |
|
|
| |
| if (elem.name === 'option' && name === 'value') { |
| return text(elem.children); |
| } |
|
|
| |
| if ( |
| elem.name === 'input' && |
| (elem.attribs['type'] === 'radio' || elem.attribs['type'] === 'checkbox') && |
| name === 'value' |
| ) { |
| return 'on'; |
| } |
|
|
| return undefined; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function setAttr(el: Element, name: string, value: string | null) { |
| if (value === null) { |
| removeAttribute(el, name); |
| } else { |
| el.attribs[name] = `${value}`; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function attr<T extends AnyNode>( |
| this: Cheerio<T>, |
| name: string, |
| ): string | undefined; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function attr<T extends AnyNode>( |
| this: Cheerio<T>, |
| ): Record<string, string> | undefined; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function attr<T extends AnyNode>( |
| this: Cheerio<T>, |
| name: string, |
| value?: |
| | string |
| | null |
| | ((this: Element, i: number, attrib: string) => string | null), |
| ): Cheerio<T>; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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> { |
| |
| 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); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function getProp( |
| el: Element, |
| name: string, |
| xmlMode?: boolean, |
| ): string | undefined | Element[keyof Element] { |
| return name in el |
| ? |
| el[name] |
| : !xmlMode && rboolean.test(name) |
| ? getAttr(el, name, false) !== undefined |
| : getAttr(el, name, xmlMode); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function setProp(el: Element, name: string, value: unknown, xmlMode?: boolean) { |
| if (name in el) { |
| |
| 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; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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; |
| |
| |
| |
| |
| |
| |
| |
| export function prop<T extends AnyNode>( |
| this: Cheerio<T>, |
| name: 'style', |
| ): StyleProp | undefined; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function prop<T extends AnyNode>( |
| this: Cheerio<T>, |
| name: 'href' | 'src', |
| ): string | undefined; |
| |
| |
| |
| |
| |
| |
| export function prop<T extends AnyNode, K extends keyof Element>( |
| this: Cheerio<T>, |
| name: K, |
| ): Element[K]; |
| |
| |
| |
| |
| |
| |
| |
| 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>; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function prop<T extends AnyNode>( |
| this: Cheerio<T>, |
| map: Record<string, string | Element[keyof Element] | boolean>, |
| ): Cheerio<T>; |
| |
| |
| |
| |
| |
| |
| |
| 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>; |
| |
| |
| |
| |
| |
| |
| 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; |
| } |
|
|
| |
| |
| |
| |
| |
| interface DataElement extends Element { |
| |
| data?: Record<string, unknown>; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| 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; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| 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 { |
| |
| } |
| } |
| return value; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function data<T extends AnyNode>( |
| this: Cheerio<T>, |
| name: string, |
| ): unknown | undefined; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function data<T extends AnyNode>( |
| this: Cheerio<T>, |
| ): Record<string, unknown>; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function data<T extends AnyNode>( |
| this: Cheerio<T>, |
| name: string, |
| value: unknown, |
| ): Cheerio<T>; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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 ??= {}; |
|
|
| |
| if (name == null) { |
| return readAllData(dataEl); |
| } |
|
|
| |
| 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); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function val<T extends AnyNode>( |
| this: Cheerio<T>, |
| ): string | undefined | string[]; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| function removeAttribute(elem: Element, name: string) { |
| if (!elem.attribs || !hasOwn.call(elem.attribs, name)) return; |
|
|
| delete elem.attribs[name]; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| function splitNames(names?: string): string[] { |
| return names ? names.trim().split(rspace) : []; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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; |
| }); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function addClass<T extends AnyNode, R extends ArrayLike<T>>( |
| this: R, |
| value?: |
| | string |
| | ((this: Element, i: number, className: string) => string | undefined), |
| ): R { |
| |
| 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)); |
| } |
| }); |
| } |
|
|
| |
| 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 (!isTag(el)) continue; |
|
|
| |
| const className = getAttr(el, 'class', false); |
|
|
| if (className) { |
| let setClass = ` ${className} `; |
|
|
| |
| 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; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function removeClass<T extends AnyNode, R extends ArrayLike<T>>( |
| this: R, |
| name?: |
| | string |
| | ((this: Element, i: number, className: string) => string | undefined), |
| ): R { |
| |
| 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) { |
| |
| 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; |
|
|
| |
| |
| |
| |
| j--; |
| } |
| } |
| if (changed) { |
| el.attribs['class'] = elClasses.join(' '); |
| } |
| } |
| }); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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 { |
| |
| if (typeof value === 'function') { |
| return domEach(this, (el, i) => { |
| if (isTag(el)) { |
| toggleClass.call( |
| [el], |
| value.call(el, i, el.attribs['class'] || '', stateVal), |
| stateVal, |
| ); |
| } |
| }); |
| } |
|
|
| |
| 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 (!isTag(el)) continue; |
|
|
| const elementClasses = splitNames(el.attribs['class']); |
|
|
| |
| for (let j = 0; j < numClasses; j++) { |
| |
| const index = elementClasses.indexOf(classNames[j]); |
|
|
| |
| if (state >= 0 && index < 0) { |
| elementClasses.push(classNames[j]); |
| } else if (state <= 0 && index >= 0) { |
| |
| elementClasses.splice(index, 1); |
| } |
| } |
|
|
| el.attribs['class'] = elementClasses.join(' '); |
| } |
|
|
| return this; |
| } |
|
|