import type { AnyNode, Element } from 'domhandler'; import type { Cheerio } from '../cheerio.js'; import type { prop } from './attributes.js'; type ExtractDescriptorFn = ( el: Element, key: string, // TODO: This could be typed with ExtractedMap obj: Record, ) => unknown; interface ExtractDescriptor { selector: string; value?: string | ExtractDescriptorFn | ExtractMap; } type ExtractValue = string | ExtractDescriptor | [string | ExtractDescriptor]; export type ExtractMap = Record; type ExtractedValue = V extends [ string | ExtractDescriptor, ] ? NonNullable>[] : V extends string ? string | undefined : V extends ExtractDescriptor ? V['value'] extends infer U ? U extends ExtractMap ? ExtractedMap | undefined : U extends ExtractDescriptorFn ? ReturnType | undefined : ReturnType | undefined : never : never; export type ExtractedMap = { [key in keyof M]: ExtractedValue; }; function getExtractDescr( descr: string | ExtractDescriptor, ): Required { if (typeof descr === 'string') { return { selector: descr, value: 'textContent' }; } return { selector: descr.selector, value: descr.value ?? 'textContent', }; } /** * Extract multiple values from a document, and store them in an object. * * @param map - An object containing key-value pairs. The keys are the names of * the properties to be created on the object, and the values are the * selectors to be used to extract the values. * @returns An object containing the extracted values. */ export function extract( this: Cheerio, map: M, ): ExtractedMap { const ret: Record = {}; for (const key in map) { const descr = map[key]; const isArray = Array.isArray(descr); const { selector, value } = getExtractDescr(isArray ? descr[0] : descr); const fn: ExtractDescriptorFn = typeof value === 'function' ? value : typeof value === 'string' ? (el: Element) => this._make(el).prop(value) : (el: Element) => this._make(el).extract(value); if (isArray) { ret[key] = this._findBySelector(selector, Number.POSITIVE_INFINITY) .map((_, el) => fn(el, key, ret)) .get(); } else { const $ = this._findBySelector(selector, 1); ret[key] = $.length > 0 ? fn($[0], key, ret) : undefined; } } return ret as ExtractedMap; }