|
|
import type { AnyNode, Element } from 'domhandler'; |
|
|
import type { Cheerio } from '../cheerio.js'; |
|
|
import type { prop } from './attributes.js'; |
|
|
|
|
|
type ExtractDescriptorFn = ( |
|
|
el: Element, |
|
|
key: string, |
|
|
|
|
|
obj: Record<string, unknown>, |
|
|
) => unknown; |
|
|
|
|
|
interface ExtractDescriptor { |
|
|
selector: string; |
|
|
value?: string | ExtractDescriptorFn | ExtractMap; |
|
|
} |
|
|
|
|
|
type ExtractValue = string | ExtractDescriptor | [string | ExtractDescriptor]; |
|
|
|
|
|
export type ExtractMap = Record<string, ExtractValue>; |
|
|
|
|
|
type ExtractedValue<V extends ExtractValue> = V extends [ |
|
|
string | ExtractDescriptor, |
|
|
] |
|
|
? NonNullable<ExtractedValue<V[0]>>[] |
|
|
: V extends string |
|
|
? string | undefined |
|
|
: V extends ExtractDescriptor |
|
|
? V['value'] extends infer U |
|
|
? U extends ExtractMap |
|
|
? ExtractedMap<U> | undefined |
|
|
: U extends ExtractDescriptorFn |
|
|
? ReturnType<U> | undefined |
|
|
: ReturnType<typeof prop> | undefined |
|
|
: never |
|
|
: never; |
|
|
|
|
|
export type ExtractedMap<M extends ExtractMap> = { |
|
|
[key in keyof M]: ExtractedValue<M[key]>; |
|
|
}; |
|
|
|
|
|
function getExtractDescr( |
|
|
descr: string | ExtractDescriptor, |
|
|
): Required<ExtractDescriptor> { |
|
|
if (typeof descr === 'string') { |
|
|
return { selector: descr, value: 'textContent' }; |
|
|
} |
|
|
|
|
|
return { |
|
|
selector: descr.selector, |
|
|
value: descr.value ?? 'textContent', |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function extract<M extends ExtractMap, T extends AnyNode>( |
|
|
this: Cheerio<T>, |
|
|
map: M, |
|
|
): ExtractedMap<M> { |
|
|
const ret: Record<string, unknown> = {}; |
|
|
|
|
|
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<M>; |
|
|
} |
|
|
|