Initial commit: MangaReader iOS App

 Features:
- App iOS completa para leer manga sin publicidad
- Scraper con WKWebView para manhwaweb.com
- Sistema de descargas offline
- Lector con zoom y navegación
- Favoritos y progreso de lectura
- Compatible con iOS 15+ y Sideloadly/3uTools

📦 Contenido:
- Backend Node.js con Puppeteer (opcional)
- App iOS con SwiftUI
- Scraper de capítulos e imágenes
- Sistema de almacenamiento local
- Testing completo
- Documentación exhaustiva

🧪 Prueba: Capítulo 789 de One Piece descargado exitosamente
  - 21 páginas descargadas
  - 4.68 MB total
  - URLs verificadas y funcionales

🎉 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-02-04 15:34:18 +01:00
commit b474182dd9
6394 changed files with 1063909 additions and 0 deletions

1145
backend/node_modules/cheerio/src/api/attributes.ts generated vendored Normal file

File diff suppressed because it is too large Load Diff

224
backend/node_modules/cheerio/src/api/css.ts generated vendored Normal file
View File

@@ -0,0 +1,224 @@
import { domEach } from '../utils.js';
import { isTag, type Element, type AnyNode } from 'domhandler';
import type { Cheerio } from '../cheerio.js';
/**
* Get the value of a style property for the first element in the set of matched
* elements.
*
* @category CSS
* @param names - Optionally the names of the properties of interest.
* @returns A map of all of the style properties.
* @see {@link https://api.jquery.com/css/}
*/
export function css<T extends AnyNode>(
this: Cheerio<T>,
names?: string[],
): Record<string, string> | undefined;
/**
* Get the value of a style property for the first element in the set of matched
* elements.
*
* @category CSS
* @param name - The name of the property.
* @returns The property value for the given name.
* @see {@link https://api.jquery.com/css/}
*/
export function css<T extends AnyNode>(
this: Cheerio<T>,
name: string,
): string | undefined;
/**
* Set one CSS property for every matched element.
*
* @category CSS
* @param prop - The name of the property.
* @param val - The new value.
* @returns The instance itself.
* @see {@link https://api.jquery.com/css/}
*/
export function css<T extends AnyNode>(
this: Cheerio<T>,
prop: string,
val:
| string
| ((this: Element, i: number, style: string) => string | undefined),
): Cheerio<T>;
/**
* Set multiple CSS properties for every matched element.
*
* @category CSS
* @param map - A map of property names and values.
* @returns The instance itself.
* @see {@link https://api.jquery.com/css/}
*/
export function css<T extends AnyNode>(
this: Cheerio<T>,
map: Record<string, string>,
): Cheerio<T>;
/**
* Set multiple CSS properties for every matched element.
*
* @category CSS
* @param prop - The names of the properties.
* @param val - The new values.
* @returns The instance itself.
* @see {@link https://api.jquery.com/css/}
*/
export function css<T extends AnyNode>(
this: Cheerio<T>,
prop?: string | string[] | Record<string, string>,
val?:
| string
| ((this: Element, i: number, style: string) => string | undefined),
): Cheerio<T> | Record<string, string> | string | undefined {
if (
(prop != null && val != null) ||
// When `prop` is a "plain" object
(typeof prop === 'object' && !Array.isArray(prop))
) {
return domEach(this, (el, i) => {
if (isTag(el)) {
// `prop` can't be an array here anymore.
setCss(el, prop as string, val, i);
}
});
}
if (this.length === 0) {
return undefined;
}
return getCss(this[0], prop as string);
}
/**
* Set styles of all elements.
*
* @private
* @param el - Element to set style of.
* @param prop - Name of property.
* @param value - Value to set property to.
* @param idx - Optional index within the selection.
*/
function setCss(
el: Element,
prop: string | Record<string, string>,
value:
| string
| ((this: Element, i: number, style: string) => string | undefined)
| undefined,
idx: number,
) {
if (typeof prop === 'string') {
const styles = getCss(el);
const val =
typeof value === 'function' ? value.call(el, idx, styles[prop]) : value;
if (val === '') {
delete styles[prop];
} else if (val != null) {
styles[prop] = val;
}
el.attribs['style'] = stringify(styles);
} else if (typeof prop === 'object') {
const keys = Object.keys(prop);
for (let i = 0; i < keys.length; i++) {
const k = keys[i];
setCss(el, k, prop[k], i);
}
}
}
/**
* Get the parsed styles of the first element.
*
* @private
* @category CSS
* @param el - Element to get styles from.
* @param props - Optionally the names of the properties of interest.
* @returns The parsed styles.
*/
function getCss(el: AnyNode, props?: string[]): Record<string, string>;
/**
* Get a property from the parsed styles of the first element.
*
* @private
* @category CSS
* @param el - Element to get styles from.
* @param prop - Name of the prop.
* @returns The value of the property.
*/
function getCss(el: AnyNode, prop: string): string | undefined;
function getCss(
el: AnyNode,
prop?: string | string[],
): Record<string, string> | string | undefined {
if (!el || !isTag(el)) return;
const styles = parse(el.attribs['style']);
if (typeof prop === 'string') {
return styles[prop];
}
if (Array.isArray(prop)) {
const newStyles: Record<string, string> = {};
for (const item of prop) {
if (styles[item] != null) {
newStyles[item] = styles[item];
}
}
return newStyles;
}
return styles;
}
/**
* Stringify `obj` to styles.
*
* @private
* @category CSS
* @param obj - Object to stringify.
* @returns The serialized styles.
*/
function stringify(obj: Record<string, string>): string {
return Object.keys(obj).reduce(
(str, prop) => `${str}${str ? ' ' : ''}${prop}: ${obj[prop]};`,
'',
);
}
/**
* Parse `styles`.
*
* @private
* @category CSS
* @param styles - Styles to be parsed.
* @returns The parsed styles.
*/
function parse(styles: string): Record<string, string> {
styles = (styles || '').trim();
if (!styles) return {};
const obj: Record<string, string> = {};
let key: string | undefined;
for (const str of styles.split(';')) {
const n = str.indexOf(':');
// If there is no :, or if it is the first/last character, add to the previous item's value
if (n < 1 || n === str.length - 1) {
const trimmed = str.trimEnd();
if (trimmed.length > 0 && key !== undefined) {
obj[key] += `;${trimmed}`;
}
} else {
key = str.slice(0, n).trim();
obj[key] = str.slice(n + 1).trim();
}
}
return obj;
}

92
backend/node_modules/cheerio/src/api/extract.ts generated vendored Normal file
View File

@@ -0,0 +1,92 @@
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<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',
};
}
/**
* 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<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>;
}

103
backend/node_modules/cheerio/src/api/forms.ts generated vendored Normal file
View File

@@ -0,0 +1,103 @@
import { isTag, type AnyNode } from 'domhandler';
import type { Cheerio } from '../cheerio.js';
/*
* https://github.com/jquery/jquery/blob/2.1.3/src/manipulation/var/rcheckableType.js
* https://github.com/jquery/jquery/blob/2.1.3/src/serialize.js
*/
const submittableSelector = 'input,select,textarea,keygen';
const r20 = /%20/g;
const rCRLF = /\r?\n/g;
/**
* Encode a set of form elements as a string for submission.
*
* @category Forms
* @example
*
* ```js
* $('<form><input name="foo" value="bar" /></form>').serialize();
* //=> 'foo=bar'
* ```
*
* @returns The serialized form.
* @see {@link https://api.jquery.com/serialize/}
*/
export function serialize<T extends AnyNode>(this: Cheerio<T>): string {
// Convert form elements into name/value objects
const arr = this.serializeArray();
// Serialize each element into a key/value string
const retArr = arr.map(
(data) =>
`${encodeURIComponent(data.name)}=${encodeURIComponent(data.value)}`,
);
// Return the resulting serialization
return retArr.join('&').replace(r20, '+');
}
/**
* Encode a set of form elements as an array of names and values.
*
* @category Forms
* @example
*
* ```js
* $('<form><input name="foo" value="bar" /></form>').serializeArray();
* //=> [ { name: 'foo', value: 'bar' } ]
* ```
*
* @returns The serialized form.
* @see {@link https://api.jquery.com/serializeArray/}
*/
export function serializeArray<T extends AnyNode>(
this: Cheerio<T>,
): {
name: string;
value: string;
}[] {
// Resolve all form elements from either forms or collections of form elements
return this.map((_, elem) => {
const $elem = this._make(elem);
if (isTag(elem) && elem.name === 'form') {
return $elem.find(submittableSelector).toArray();
}
return $elem.filter(submittableSelector).toArray();
})
.filter(
// Verify elements have a name (`attr.name`) and are not disabled (`:enabled`)
'[name!=""]:enabled' +
// And cannot be clicked (`[type=submit]`) or are used in `x-www-form-urlencoded` (`[type=file]`)
':not(:submit, :button, :image, :reset, :file)' +
// And are either checked/don't have a checkable state
':matches([checked], :not(:checkbox, :radio))',
// Convert each of the elements to its value(s)
)
.map<
AnyNode,
{
name: string;
value: string;
}
>((_, elem) => {
const $elem = this._make(elem);
const name = $elem.attr('name')!; // We have filtered for elements with a name before.
// If there is no value set (e.g. `undefined`, `null`), then default value to empty
const value = $elem.val() ?? '';
// If we have an array of values (e.g. `<select multiple>`), return an array of key/value pairs
if (Array.isArray(value)) {
return value.map((val) =>
/*
* We trim replace any line endings (e.g. `\r` or `\r\n` with `\r\n`) to guarantee consistency across platforms
* These can occur inside of `<textarea>'s`
*/
({ name, value: val.replace(rCRLF, '\r\n') }),
);
}
// Otherwise (e.g. `<input type="text">`, return only one key/value pair
return { name, value: value.replace(rCRLF, '\r\n') };
})
.toArray();
}

1115
backend/node_modules/cheerio/src/api/manipulation.ts generated vendored Normal file

File diff suppressed because it is too large Load Diff

1175
backend/node_modules/cheerio/src/api/traversing.ts generated vendored Normal file

File diff suppressed because it is too large Load Diff

143
backend/node_modules/cheerio/src/cheerio.ts generated vendored Normal file
View File

@@ -0,0 +1,143 @@
/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */
import type { InternalOptions } from './options.js';
import type { AnyNode, Document, ParentNode } from 'domhandler';
import type { BasicAcceptedElems } from './types.js';
import * as Attributes from './api/attributes.js';
import * as Traversing from './api/traversing.js';
import * as Manipulation from './api/manipulation.js';
import * as Css from './api/css.js';
import * as Forms from './api/forms.js';
import * as Extract from './api/extract.js';
type MethodsType = typeof Attributes &
typeof Traversing &
typeof Manipulation &
typeof Css &
typeof Forms &
typeof Extract;
/**
* The cheerio class is the central class of the library. It wraps a set of
* elements and provides an API for traversing, modifying, and interacting with
* the set.
*
* Loading a document will return the Cheerio class bound to the root element of
* the document. The class will be instantiated when querying the document (when
* calling `$('selector')`).
*
* @example This is the HTML markup we will be using in all of the API examples:
*
* ```html
* <ul id="fruits">
* <li class="apple">Apple</li>
* <li class="orange">Orange</li>
* <li class="pear">Pear</li>
* </ul>
* ```
*/
export abstract class Cheerio<T> implements ArrayLike<T> {
length = 0;
[index: number]: T;
options: InternalOptions;
/**
* The root of the document. Can be set by using the `root` argument of the
* constructor.
*
* @private
*/
_root: Cheerio<Document> | null;
/**
* Instance of cheerio. Methods are specified in the modules. Usage of this
* constructor is not recommended. Please use `$.load` instead.
*
* @private
* @param elements - The new selection.
* @param root - Sets the root node.
* @param options - Options for the instance.
*/
constructor(
elements: ArrayLike<T> | undefined,
root: Cheerio<Document> | null,
options: InternalOptions,
) {
this.options = options;
this._root = root;
if (elements) {
for (let idx = 0; idx < elements.length; idx++) {
this[idx] = elements[idx];
}
this.length = elements.length;
}
}
prevObject: Cheerio<any> | undefined;
/**
* Make a cheerio object.
*
* @private
* @param dom - The contents of the new object.
* @param context - The context of the new object.
* @returns The new cheerio object.
*/
abstract _make<T>(
dom: ArrayLike<T> | T | string,
context?: BasicAcceptedElems<AnyNode>,
): Cheerio<T>;
/**
* Parses some content.
*
* @private
* @param content - Content to parse.
* @param options - Options for parsing.
* @param isDocument - Allows parser to be switched to fragment mode.
* @returns A document containing the `content`.
*/
abstract _parse(
content: string | Document | AnyNode | AnyNode[] | Buffer,
options: InternalOptions,
isDocument: boolean,
context: ParentNode | null,
): Document;
/**
* Render an element or a set of elements.
*
* @private
* @param dom - DOM to render.
* @returns The rendered DOM.
*/
abstract _render(dom: AnyNode | ArrayLike<AnyNode>): string;
}
export interface Cheerio<T> extends MethodsType, Iterable<T> {
cheerio: '[cheerio object]';
splice: typeof Array.prototype.splice;
}
/** Set a signature of the object. */
Cheerio.prototype.cheerio = '[cheerio object]';
/*
* Make cheerio an array-like object
*/
Cheerio.prototype.splice = Array.prototype.splice;
// Support for (const element of $(...)) iteration:
Cheerio.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
// Plug in the API
Object.assign(
Cheerio.prototype,
Attributes,
Traversing,
Manipulation,
Css,
Forms,
Extract,
);

10
backend/node_modules/cheerio/src/index-browser.mts generated vendored Normal file
View File

@@ -0,0 +1,10 @@
export type * from './types.js';
export type {
Cheerio,
CheerioAPI,
CheerioOptions,
HTMLParser2Options,
} from './slim.js';
export { contains, merge } from './static.js';
export * from './load-parse.js';

294
backend/node_modules/cheerio/src/index.ts generated vendored Normal file
View File

@@ -0,0 +1,294 @@
/**
* @file Batteries-included version of Cheerio. This module includes several
* convenience methods for loading documents from various sources.
*/
export * from './load-parse.js';
export { contains, merge } from './static.js';
export type * from './types.js';
export type {
Cheerio,
CheerioAPI,
CheerioOptions,
HTMLParser2Options,
} from './slim.js';
import { adapter as htmlparser2Adapter } from 'parse5-htmlparser2-tree-adapter';
import * as htmlparser2 from 'htmlparser2';
import { ParserStream as Parse5Stream } from 'parse5-parser-stream';
import {
decodeBuffer,
DecodeStream,
type SnifferOptions,
} from 'encoding-sniffer';
import * as undici from 'undici';
import MIMEType from 'whatwg-mimetype';
import { Writable, finished } from 'node:stream';
import type { CheerioAPI } from './load.js';
import {
flattenOptions,
type InternalOptions,
type CheerioOptions,
} from './options.js';
import { load } from './load-parse.js';
/**
* Sniffs the encoding of a buffer, then creates a querying function bound to a
* document created from the buffer.
*
* @category Loading
* @example
*
* ```js
* import * as cheerio from 'cheerio';
*
* const buffer = fs.readFileSync('index.html');
* const $ = cheerio.loadBuffer(buffer);
* ```
*
* @param buffer - The buffer to sniff the encoding of.
* @param options - The options to pass to Cheerio.
* @returns The loaded document.
*/
export function loadBuffer(
buffer: Buffer,
options: DecodeStreamOptions = {},
): CheerioAPI {
const opts = flattenOptions(options);
const str = decodeBuffer(buffer, {
defaultEncoding: opts?.xmlMode ? 'utf8' : 'windows-1252',
...options.encoding,
});
return load(str, opts);
}
function _stringStream(
options: InternalOptions | undefined,
cb: (err: Error | null | undefined, $: CheerioAPI) => void,
): Writable {
if (options?._useHtmlParser2) {
const parser = htmlparser2.createDocumentStream(
(err, document) => cb(err, load(document, options)),
options,
);
return new Writable({
decodeStrings: false,
write(chunk, _encoding, callback) {
if (typeof chunk !== 'string') {
throw new TypeError('Expected a string');
}
parser.write(chunk);
callback();
},
final(callback) {
parser.end();
callback();
},
});
}
options ??= {};
options.treeAdapter ??= htmlparser2Adapter;
if (options.scriptingEnabled !== false) {
options.scriptingEnabled = true;
}
const stream = new Parse5Stream(options);
finished(stream, (err) => cb(err, load(stream.document, options)));
return stream;
}
/**
* Creates a stream that parses a sequence of strings into a document.
*
* The stream is a `Writable` stream that accepts strings. When the stream is
* finished, the callback is called with the loaded document.
*
* @category Loading
* @example
*
* ```js
* import * as cheerio from 'cheerio';
* import * as fs from 'fs';
*
* const writeStream = cheerio.stringStream({}, (err, $) => {
* if (err) {
* // Handle error
* }
*
* console.log($('h1').text());
* // Output: Hello, world!
* });
*
* fs.createReadStream('my-document.html', { encoding: 'utf8' }).pipe(
* writeStream,
* );
* ```
*
* @param options - The options to pass to Cheerio.
* @param cb - The callback to call when the stream is finished.
* @returns The writable stream.
*/
export function stringStream(
options: CheerioOptions,
cb: (err: Error | null | undefined, $: CheerioAPI) => void,
): Writable {
return _stringStream(flattenOptions(options), cb);
}
export interface DecodeStreamOptions extends CheerioOptions {
encoding?: SnifferOptions;
}
/**
* Parses a stream of buffers into a document.
*
* The stream is a `Writable` stream that accepts buffers. When the stream is
* finished, the callback is called with the loaded document.
*
* @category Loading
* @param options - The options to pass to Cheerio.
* @param cb - The callback to call when the stream is finished.
* @returns The writable stream.
*/
export function decodeStream(
options: DecodeStreamOptions,
cb: (err: Error | null | undefined, $: CheerioAPI) => void,
): Writable {
const { encoding = {}, ...cheerioOptions } = options;
const opts = flattenOptions(cheerioOptions);
// Set the default encoding to UTF-8 for XML mode
encoding.defaultEncoding ??= opts?.xmlMode ? 'utf8' : 'windows-1252';
const decodeStream = new DecodeStream(encoding);
const loadStream = _stringStream(opts, cb);
decodeStream.pipe(loadStream);
return decodeStream;
}
type UndiciStreamOptions = Omit<
undici.Dispatcher.RequestOptions<unknown>,
'path'
>;
export interface CheerioRequestOptions extends DecodeStreamOptions {
/** The options passed to `undici`'s `stream` method. */
requestOptions?: UndiciStreamOptions;
}
const defaultRequestOptions: UndiciStreamOptions = {
method: 'GET',
// Set an Accept header
headers: {
accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
},
};
/**
* `fromURL` loads a document from a URL.
*
* By default, redirects are allowed and non-2xx responses are rejected.
*
* @category Loading
* @example
*
* ```js
* import * as cheerio from 'cheerio';
*
* const $ = await cheerio.fromURL('https://example.com');
* ```
*
* @param url - The URL to load the document from.
* @param options - The options to pass to Cheerio.
* @returns The loaded document.
*/
export async function fromURL(
url: string | URL,
options: CheerioRequestOptions = {},
): Promise<CheerioAPI> {
const {
requestOptions = defaultRequestOptions,
encoding = {},
...cheerioOptions
} = options;
let undiciStream: Promise<undici.Dispatcher.StreamData<unknown>> | undefined;
// Add headers if none were supplied.
const urlObject = typeof url === 'string' ? new URL(url) : url;
const streamOptions = {
headers: defaultRequestOptions.headers,
path: urlObject.pathname + urlObject.search,
...requestOptions,
};
const promise = new Promise<CheerioAPI>((resolve, reject) => {
undiciStream = new undici.Client(urlObject.origin)
.compose(undici.interceptors.redirect({ maxRedirections: 5 }))
.stream(streamOptions, (res) => {
if (res.statusCode < 200 || res.statusCode >= 300) {
throw new undici.errors.ResponseError(
'Response Error',
res.statusCode,
{
headers: res.headers,
},
);
}
const contentTypeHeader = res.headers['content-type'] ?? 'text/html';
const mimeType = new MIMEType(
Array.isArray(contentTypeHeader)
? contentTypeHeader[0]
: contentTypeHeader,
);
if (!mimeType.isHTML() && !mimeType.isXML()) {
throw new RangeError(
`The content-type "${mimeType.essence}" is neither HTML nor XML.`,
);
}
// Forward the charset from the header to the decodeStream.
encoding.transportLayerEncodingLabel =
mimeType.parameters.get('charset');
/*
* If we allow redirects, we will have entries in the history.
* The last entry will be the final URL.
*/
const history = (
res.context as
| {
history?: URL[];
}
| undefined
)?.history;
// Set the `baseURI` to the final URL.
const baseURI = history ? history[history.length - 1] : urlObject;
const opts: DecodeStreamOptions = {
encoding,
// Set XML mode based on the MIME type.
xmlMode: mimeType.isXML(),
baseURI,
...cheerioOptions,
};
return decodeStream(opts, (err, $) => (err ? reject(err) : resolve($)));
});
});
// Let's make sure the request is completed before returning the promise.
await undiciStream;
return promise;
}

39
backend/node_modules/cheerio/src/load-parse.ts generated vendored Normal file
View File

@@ -0,0 +1,39 @@
import { type CheerioAPI, getLoad } from './load.js';
import { getParse } from './parse.js';
import { renderWithParse5, parseWithParse5 } from './parsers/parse5-adapter.js';
import type { CheerioOptions } from './options.js';
import renderWithHtmlparser2 from 'dom-serializer';
import { parseDocument as parseWithHtmlparser2 } from 'htmlparser2';
import type { AnyNode } from 'domhandler';
const parse = getParse((content, options, isDocument, context) =>
options._useHtmlParser2
? parseWithHtmlparser2(content, options)
: parseWithParse5(content, options, isDocument, context),
);
// Duplicate docs due to https://github.com/TypeStrong/typedoc/issues/1616
/**
* Create a querying function, bound to a document created from the provided
* markup.
*
* Note that similar to web browser contexts, this operation may introduce
* `<html>`, `<head>`, and `<body>` elements; set `isDocument` to `false` to
* switch to fragment mode and disable this.
*
* @category Loading
* @param content - Markup to be loaded.
* @param options - Options for the created instance.
* @param isDocument - Allows parser to be switched to fragment mode.
* @returns The loaded document.
* @see {@link https://cheerio.js.org/docs/basics/loading#load} for additional usage information.
*/
export const load: (
content: string | AnyNode | AnyNode[] | Buffer,
options?: CheerioOptions | null,
isDocument?: boolean,
) => CheerioAPI = getLoad(parse, (dom, options) =>
options._useHtmlParser2
? renderWithHtmlparser2(dom, options)
: renderWithParse5(dom),
);

282
backend/node_modules/cheerio/src/load.ts generated vendored Normal file
View File

@@ -0,0 +1,282 @@
import {
type CheerioOptions,
type InternalOptions,
flattenOptions,
} from './options.js';
import * as staticMethods from './static.js';
import { Cheerio } from './cheerio.js';
import { isHtml, isCheerio } from './utils.js';
import type { AnyNode, Document, Element, ParentNode } from 'domhandler';
import type { SelectorType, BasicAcceptedElems } from './types.js';
import { ElementType } from 'htmlparser2';
type StaticType = typeof staticMethods;
/**
* A querying function, bound to a document created from the provided markup.
*
* Also provides several helper methods for dealing with the document as a
* whole.
*/
export interface CheerioAPI extends StaticType {
/**
* This selector method is the starting point for traversing and manipulating
* the document. Like jQuery, it's the primary method for selecting elements
* in the document.
*
* `selector` searches within the `context` scope, which searches within the
* `root` scope.
*
* @example
*
* ```js
* $('ul .pear').attr('class');
* //=> pear
*
* $('li[class=orange]').html();
* //=> Orange
*
* $('.apple', '#fruits').text();
* //=> Apple
* ```
*
* Optionally, you can also load HTML by passing the string as the selector:
*
* ```js
* $('<ul id="fruits">...</ul>');
* ```
*
* Or the context:
*
* ```js
* $('ul', '<ul id="fruits">...</ul>');
* ```
*
* Or as the root:
*
* ```js
* $('li', 'ul', '<ul id="fruits">...</ul>');
* ```
*
* @param selector - Either a selector to look for within the document, or the
* contents of a new Cheerio instance.
* @param context - Either a selector to look for within the root, or the
* contents of the document to query.
* @param root - Optional HTML document string.
*/
<T extends AnyNode, S extends string>(
selector?: S | BasicAcceptedElems<T>,
context?: BasicAcceptedElems<AnyNode> | null,
root?: BasicAcceptedElems<Document>,
options?: CheerioOptions,
): Cheerio<S extends SelectorType ? Element : T>;
/**
* The root the document was originally loaded with.
*
* @private
*/
_root: Document;
/**
* The options the document was originally loaded with.
*
* @private
*/
_options: InternalOptions;
/** Mimic jQuery's prototype alias for plugin authors. */
fn: typeof Cheerio.prototype;
/**
* The `.load` static method defined on the "loaded" Cheerio factory function
* is deprecated. Users are encouraged to instead use the `load` function
* exported by the Cheerio module.
*
* @deprecated Use the `load` function exported by the Cheerio module.
* @category Deprecated
* @example
*
* ```js
* const $ = cheerio.load('<h1>Hello, <span>world</span>.</h1>');
* ```
*/
load: ReturnType<typeof getLoad>;
}
export function getLoad(
parse: Cheerio<AnyNode>['_parse'],
render: (
dom: AnyNode | ArrayLike<AnyNode>,
options: InternalOptions,
) => string,
) {
/**
* Create a querying function, bound to a document created from the provided
* markup.
*
* Note that similar to web browser contexts, this operation may introduce
* `<html>`, `<head>`, and `<body>` elements; set `isDocument` to `false` to
* switch to fragment mode and disable this.
*
* @param content - Markup to be loaded.
* @param options - Options for the created instance.
* @param isDocument - Allows parser to be switched to fragment mode.
* @returns The loaded document.
* @see {@link https://cheerio.js.org/docs/basics/loading#load} for additional usage information.
*/
return function load(
content: string | AnyNode | AnyNode[] | Buffer,
options?: CheerioOptions | null,
isDocument = true,
): CheerioAPI {
if ((content as string | null) == null) {
throw new Error('cheerio.load() expects a string');
}
const internalOpts = flattenOptions(options);
const initialRoot = parse(content, internalOpts, isDocument, null);
/**
* Create an extended class here, so that extensions only live on one
* instance.
*/
class LoadedCheerio<T> extends Cheerio<T> {
_make<T>(
selector?: ArrayLike<T> | T | string,
context?: BasicAcceptedElems<AnyNode> | null,
): Cheerio<T> {
const cheerio = initialize(selector, context);
cheerio.prevObject = this;
return cheerio;
}
_parse(
content: string | Document | AnyNode | AnyNode[] | Buffer,
options: InternalOptions,
isDocument: boolean,
context: ParentNode | null,
) {
return parse(content, options, isDocument, context);
}
_render(dom: AnyNode | ArrayLike<AnyNode>): string {
return render(dom, this.options);
}
}
function initialize<T = AnyNode, S extends string = string>(
selector?: ArrayLike<T> | T | S,
context?: BasicAcceptedElems<AnyNode> | null,
root: BasicAcceptedElems<Document> = initialRoot,
opts?: CheerioOptions,
): Cheerio<S extends SelectorType ? Element : T> {
type Result = S extends SelectorType ? Element : T;
// $($)
if (selector && isCheerio<Result>(selector)) return selector;
const options = flattenOptions(opts, internalOpts);
const r =
typeof root === 'string'
? [parse(root, options, false, null)]
: 'length' in root
? root
: [root];
const rootInstance = isCheerio<Document>(r)
? r
: new LoadedCheerio<Document>(r, null, options);
// Add a cyclic reference, so that calling methods on `_root` never fails.
rootInstance._root = rootInstance;
// $(), $(null), $(undefined), $(false)
if (!selector) {
return new LoadedCheerio<Result>(undefined, rootInstance, options);
}
const elements: AnyNode[] | undefined =
typeof selector === 'string' && isHtml(selector)
? // $(<html>)
parse(selector, options, false, null).children
: isNode(selector)
? // $(dom)
[selector]
: Array.isArray(selector)
? // $([dom])
selector
: undefined;
const instance = new LoadedCheerio(elements, rootInstance, options);
if (elements) {
return instance as Cheerio<Result>;
}
if (typeof selector !== 'string') {
throw new TypeError('Unexpected type of selector');
}
// We know that our selector is a string now.
let search = selector;
const searchContext: Cheerio<AnyNode> | undefined = context
? // If we don't have a context, maybe we have a root, from loading
typeof context === 'string'
? isHtml(context)
? // $('li', '<ul>...</ul>')
new LoadedCheerio<Document>(
[parse(context, options, false, null)],
rootInstance,
options,
)
: // $('li', 'ul')
((search = `${context} ${search}` as S), rootInstance)
: isCheerio<AnyNode>(context)
? // $('li', $)
context
: // $('li', node), $('li', [nodes])
new LoadedCheerio<AnyNode>(
Array.isArray(context) ? context : [context],
rootInstance,
options,
)
: rootInstance;
// If we still don't have a context, return
if (!searchContext) return instance as Cheerio<Result>;
/*
* #id, .class, tag
*/
return searchContext.find(search) as Cheerio<Result>;
}
// Add in static methods & properties
Object.assign(initialize, staticMethods, {
load,
// `_root` and `_options` are used in static methods.
_root: initialRoot,
_options: internalOpts,
// Add `fn` for plugins
fn: LoadedCheerio.prototype,
// Add the prototype here to maintain `instanceof` behavior.
prototype: LoadedCheerio.prototype,
});
return initialize as CheerioAPI;
};
}
function isNode(obj: unknown): obj is AnyNode {
return (
// @ts-expect-error: TS doesn't know about the `name` property.
!!obj.name ||
// @ts-expect-error: TS doesn't know about the `type` property.
obj.type === ElementType.Root ||
// @ts-expect-error: TS doesn't know about the `type` property.
obj.type === ElementType.Text ||
// @ts-expect-error: TS doesn't know about the `type` property.
obj.type === ElementType.Comment
);
}

136
backend/node_modules/cheerio/src/options.ts generated vendored Normal file
View File

@@ -0,0 +1,136 @@
import type { DomHandlerOptions } from 'domhandler';
import type { ParserOptions as HTMLParser2ParserOptions } from 'htmlparser2';
import type { ParserOptions as Parse5ParserOptions } from 'parse5';
import type { Htmlparser2TreeAdapterMap } from 'parse5-htmlparser2-tree-adapter';
import type { Options as SelectOptions } from 'cheerio-select';
import type { DomSerializerOptions } from 'dom-serializer';
/**
* Options accepted by htmlparser2, the default parser for XML.
*
* @see https://github.com/fb55/htmlparser2/wiki/Parser-options
*/
export interface HTMLParser2Options
extends DomHandlerOptions, DomSerializerOptions, HTMLParser2ParserOptions {
/** Treat the input as an XML document. */
xmlMode?: boolean;
}
/**
* Options accepted by Cheerio.
*
* Please note that parser-specific options are _only recognized_ if the
* relevant parser is used.
*/
export interface CheerioOptions extends Parse5ParserOptions<Htmlparser2TreeAdapterMap> {
/**
* Recommended way of configuring htmlparser2 when wanting to parse XML.
*
* This will switch Cheerio to use htmlparser2.
*
* @default false
*/
xml?: HTMLParser2Options | boolean;
/**
* Enable xml mode, which will switch Cheerio to use htmlparser2.
*
* @deprecated Please use the `xml` option instead.
* @default false
*/
xmlMode?: boolean;
/** The base URI for the document. Used to resolve the `href` and `src` props. */
baseURI?: string | URL;
/**
* Is the document in quirks mode?
*
* This will lead to `.className` and `#id` being case-insensitive.
*
* @default false
*/
quirksMode?: SelectOptions['quirksMode'];
/**
* Extension point for pseudo-classes.
*
* Maps from names to either strings of functions.
*
* - A string value is a selector that the element must match to be selected.
* - A function is called with the element as its first argument, and optional
* parameters second. If it returns true, the element is selected.
*
* @example
*
* ```js
* const $ = cheerio.load(
* '<div class="foo"></div><div data-bar="boo"></div>',
* {
* pseudos: {
* // `:foo` is an alias for `div.foo`
* foo: 'div.foo',
* // `:bar(val)` is equivalent to `[data-bar=val s]`
* bar: (el, val) => el.attribs['data-bar'] === val,
* },
* },
* );
*
* $(':foo').length; // 1
* $('div:bar(boo)').length; // 1
* $('div:bar(baz)').length; // 0
* ```
*/
pseudos?: SelectOptions['pseudos'];
}
/** Internal options for Cheerio. */
export interface InternalOptions
extends HTMLParser2Options, Omit<CheerioOptions, 'xml'> {
/**
* Whether to use htmlparser2.
*
* This is set to true if `xml` is set to true.
*/
_useHtmlParser2?: boolean;
}
const defaultOpts: InternalOptions = {
_useHtmlParser2: false,
};
/**
* Flatten the options for Cheerio.
*
* This will set `_useHtmlParser2` to true if `xml` is set to true.
*
* @param options - The options to flatten.
* @param baseOptions - The base options to use.
* @returns The flattened options.
*/
export function flattenOptions(
options?: CheerioOptions | null,
baseOptions?: InternalOptions,
): InternalOptions {
if (!options) {
return baseOptions ?? defaultOpts;
}
const opts: InternalOptions = {
_useHtmlParser2: !!options.xmlMode,
...baseOptions,
...options,
};
if (options.xml) {
opts._useHtmlParser2 = true;
opts.xmlMode = true;
if (options.xml !== true) {
Object.assign(opts, options.xml);
}
} else if (options.xmlMode) {
opts._useHtmlParser2 = true;
}
return opts;
}

105
backend/node_modules/cheerio/src/parse.ts generated vendored Normal file
View File

@@ -0,0 +1,105 @@
import { removeElement } from 'domutils';
import {
type AnyNode,
Document,
type ParentNode,
isDocument as checkIsDocument,
} from 'domhandler';
import type { InternalOptions } from './options.js';
/**
* Get the parse function with options.
*
* @param parser - The parser function.
* @returns The parse function with options.
*/
export function getParse(
parser: (
content: string,
options: InternalOptions,
isDocument: boolean,
context: ParentNode | null,
) => Document,
) {
/**
* Parse a HTML string or a node.
*
* @param content - The HTML string or node.
* @param options - The parser options.
* @param isDocument - If `content` is a document.
* @param context - The context node in the DOM tree.
* @returns The parsed document node.
*/
return function parse(
content: string | Document | AnyNode | AnyNode[] | Buffer,
options: InternalOptions,
isDocument: boolean,
context: ParentNode | null,
): Document {
if (typeof Buffer !== 'undefined' && Buffer.isBuffer(content)) {
content = content.toString();
}
if (typeof content === 'string') {
return parser(content, options, isDocument, context);
}
const doc = content as AnyNode | AnyNode[] | Document;
if (!Array.isArray(doc) && checkIsDocument(doc)) {
// If `doc` is already a root, just return it
return doc;
}
// Add content to new root element
const root = new Document([]);
// Update the DOM using the root
update(doc, root);
return root;
};
}
/**
* Update the dom structure, for one changed layer.
*
* @param newChilds - The new children.
* @param parent - The new parent.
* @returns The parent node.
*/
export function update(
newChilds: AnyNode[] | AnyNode,
parent: ParentNode | null,
): ParentNode | null {
// Normalize
const arr = Array.isArray(newChilds) ? newChilds : [newChilds];
// Update parent
if (parent) {
parent.children = arr;
} else {
parent = null;
}
// Update neighbors
for (let i = 0; i < arr.length; i++) {
const node = arr[i];
// Cleanly remove existing nodes from their previous structures.
if (node.parent && node.parent.children !== arr) {
removeElement(node);
}
if (parent) {
node.prev = arr[i - 1] || null;
node.next = arr[i + 1] || null;
} else {
node.prev = node.next = null;
}
node.parent = parent;
}
return parent;
}

View File

@@ -0,0 +1,66 @@
import {
type AnyNode,
type Document,
type ParentNode,
isDocument,
} from 'domhandler';
import { parse as parseDocument, parseFragment, serializeOuter } from 'parse5';
import { adapter as htmlparser2Adapter } from 'parse5-htmlparser2-tree-adapter';
import type { InternalOptions } from '../options.js';
/**
* Parse the content with `parse5` in the context of the given `ParentNode`.
*
* @param content - The content to parse.
* @param options - A set of options to use to parse.
* @param isDocument - Whether to parse the content as a full HTML document.
* @param context - The context in which to parse the content.
* @returns The parsed content.
*/
export function parseWithParse5(
content: string,
options: InternalOptions,
isDocument: boolean,
context: ParentNode | null,
): Document {
options.treeAdapter ??= htmlparser2Adapter;
if (options.scriptingEnabled !== false) {
options.scriptingEnabled = true;
}
return isDocument
? parseDocument(content, options)
: parseFragment(context, content, options);
}
const renderOpts = { treeAdapter: htmlparser2Adapter };
/**
* Renders the given DOM tree with `parse5` and returns the result as a string.
*
* @param dom - The DOM tree to render.
* @returns The rendered document.
*/
export function renderWithParse5(dom: AnyNode | ArrayLike<AnyNode>): string {
/*
* `dom-serializer` passes over the special "root" node and renders the
* node's children in its place. To mimic this behavior with `parse5`, an
* equivalent operation must be applied to the input array.
*/
const nodes = 'length' in dom ? dom : [dom];
for (let index = 0; index < nodes.length; index += 1) {
const node = nodes[index];
if (isDocument(node)) {
Array.prototype.splice.call(nodes, index, 1, ...node.children);
}
}
let result = '';
for (let index = 0; index < nodes.length; index += 1) {
const node = nodes[index];
result += serializeOuter(node, renderOpts);
}
return result;
}

33
backend/node_modules/cheerio/src/slim.ts generated vendored Normal file
View File

@@ -0,0 +1,33 @@
/**
* @file Alternative entry point for Cheerio that always uses htmlparser2. This
* way, parse5 won't be loaded, saving some memory.
*/
import { type CheerioAPI, getLoad } from './load.js';
import { type CheerioOptions } from './options.js';
import { getParse } from './parse.js';
import type { AnyNode } from 'domhandler';
import render from 'dom-serializer';
import { parseDocument } from 'htmlparser2';
export { contains, merge } from './static.js';
export type * from './types.js';
export type { Cheerio } from './cheerio.js';
export type { CheerioOptions, HTMLParser2Options } from './options.js';
export type { CheerioAPI } from './load.js';
/**
* Create a querying function, bound to a document created from the provided
* markup.
*
* @param content - Markup to be loaded.
* @param options - Options for the created instance.
* @param isDocument - Always `false` here, as we are always using
* `htmlparser2`.
* @returns The loaded document.
* @see {@link https://cheerio.js.org#loading} for additional usage information.
*/
export const load: (
content: string | AnyNode | AnyNode[] | Buffer,
options?: CheerioOptions | null,
isDocument?: boolean,
) => CheerioAPI = getLoad(getParse(parseDocument), render);

312
backend/node_modules/cheerio/src/static.ts generated vendored Normal file
View File

@@ -0,0 +1,312 @@
import type { BasicAcceptedElems } from './types.js';
import type { CheerioAPI } from './load.js';
import type { Cheerio } from './cheerio.js';
import type { AnyNode, Document } from 'domhandler';
import { textContent } from 'domutils';
import {
type InternalOptions,
type CheerioOptions,
flattenOptions as flattenOptions,
} from './options.js';
import type { ExtractedMap, ExtractMap } from './api/extract.js';
/**
* Helper function to render a DOM.
*
* @param that - Cheerio instance to render.
* @param dom - The DOM to render. Defaults to `that`'s root.
* @param options - Options for rendering.
* @returns The rendered document.
*/
function render(
that: CheerioAPI,
dom: BasicAcceptedElems<AnyNode> | undefined,
options: InternalOptions,
): string {
if (!that) return '';
return that(dom ?? that._root.children, null, undefined, options).toString();
}
/**
* Checks if a passed object is an options object.
*
* @param dom - Object to check if it is an options object.
* @param options - Options object.
* @returns Whether the object is an options object.
*/
function isOptions(
dom?: BasicAcceptedElems<AnyNode> | CheerioOptions | null,
options?: CheerioOptions,
): dom is CheerioOptions {
return (
!options &&
typeof dom === 'object' &&
dom != null &&
!('length' in dom) &&
!('type' in dom)
);
}
/**
* Renders the document.
*
* @category Static
* @param options - Options for the renderer.
* @returns The rendered document.
*/
export function html(this: CheerioAPI, options?: CheerioOptions): string;
/**
* Renders the document.
*
* @category Static
* @param dom - Element to render.
* @param options - Options for the renderer.
* @returns The rendered document.
*/
export function html(
this: CheerioAPI,
dom?: BasicAcceptedElems<AnyNode>,
options?: CheerioOptions,
): string;
export function html(
this: CheerioAPI,
dom?: BasicAcceptedElems<AnyNode> | CheerioOptions,
options?: CheerioOptions,
): string {
/*
* Be flexible about parameters, sometimes we call html(),
* with options as only parameter
* check dom argument for dom element specific properties
* assume there is no 'length' or 'type' properties in the options object
*/
const toRender = isOptions(dom) ? ((options = dom), undefined) : dom;
/*
* Sometimes `$.html()` is used without preloading html,
* so fallback non-existing options to the default ones.
*/
const opts = {
...this?._options,
...flattenOptions(options),
};
return render(this, toRender, opts);
}
/**
* Render the document as XML.
*
* @category Static
* @param dom - Element to render.
* @returns THe rendered document.
*/
export function xml(
this: CheerioAPI,
dom?: BasicAcceptedElems<AnyNode>,
): string {
const options = { ...this._options, xmlMode: true };
return render(this, dom, options);
}
/**
* Render the document as text.
*
* This returns the `textContent` of the passed elements. The result will
* include the contents of `<script>` and `<style>` elements. To avoid this, use
* `.prop('innerText')` instead.
*
* @category Static
* @param elements - Elements to render.
* @returns The rendered document.
*/
export function text(
this: CheerioAPI | void,
elements?: ArrayLike<AnyNode>,
): string {
const elems = elements ?? (this ? this.root() : []);
let ret = '';
for (let i = 0; i < elems.length; i++) {
ret += textContent(elems[i]);
}
return ret;
}
/**
* Parses a string into an array of DOM nodes. The `context` argument has no
* meaning for Cheerio, but it is maintained for API compatibility with jQuery.
*
* @category Static
* @param data - Markup that will be parsed.
* @param context - Will be ignored. If it is a boolean it will be used as the
* value of `keepScripts`.
* @param keepScripts - If false all scripts will be removed.
* @returns The parsed DOM.
* @alias Cheerio.parseHTML
* @see {@link https://api.jquery.com/jQuery.parseHTML/}
*/
export function parseHTML(
this: CheerioAPI,
data: string,
context?: unknown,
keepScripts?: boolean,
): AnyNode[];
export function parseHTML(this: CheerioAPI, data?: '' | null): null;
export function parseHTML(
this: CheerioAPI,
data?: string | null,
context?: unknown,
keepScripts = typeof context === 'boolean' ? context : false,
): AnyNode[] | null {
if (!data || typeof data !== 'string') {
return null;
}
if (typeof context === 'boolean') {
keepScripts = context;
}
const parsed = this.load(data, this._options, false);
if (!keepScripts) {
parsed('script').remove();
}
/*
* The `children` array is used by Cheerio internally to group elements that
* share the same parents. When nodes created through `parseHTML` are
* inserted into previously-existing DOM structures, they will be removed
* from the `children` array. The results of `parseHTML` should remain
* constant across these operations, so a shallow copy should be returned.
*/
return [...parsed.root()[0].children];
}
/**
* Sometimes you need to work with the top-level root element. To query it, you
* can use `$.root()`.
*
* @category Static
* @example
*
* ```js
* $.root().append('<ul id="vegetables"></ul>').html();
* //=> <ul id="fruits">...</ul><ul id="vegetables"></ul>
* ```
*
* @returns Cheerio instance wrapping the root node.
* @alias Cheerio.root
*/
export function root(this: CheerioAPI): Cheerio<Document> {
return this(this._root);
}
/**
* Checks to see if the `contained` DOM element is a descendant of the
* `container` DOM element.
*
* @category Static
* @param container - Potential parent node.
* @param contained - Potential child node.
* @returns Indicates if the nodes contain one another.
* @alias Cheerio.contains
* @see {@link https://api.jquery.com/jQuery.contains/}
*/
export function contains(container: AnyNode, contained: AnyNode): boolean {
// According to the jQuery API, an element does not "contain" itself
if (contained === container) {
return false;
}
/*
* Step up the descendants, stopping when the root element is reached
* (signaled by `.parent` returning a reference to the same object)
*/
let next: AnyNode | null = contained;
while (next && next !== next.parent) {
next = next.parent;
if (next === container) {
return true;
}
}
return false;
}
/**
* Extract multiple values from a document, and store them in an object.
*
* @category Static
* @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<M extends ExtractMap>(
this: CheerioAPI,
map: M,
): ExtractedMap<M> {
return this.root().extract(map);
}
type Writable<T> = { -readonly [P in keyof T]: T[P] };
/**
* $.merge().
*
* @category Static
* @param arr1 - First array.
* @param arr2 - Second array.
* @returns `arr1`, with elements of `arr2` inserted.
* @alias Cheerio.merge
* @see {@link https://api.jquery.com/jQuery.merge/}
*/
export function merge<T>(
arr1: Writable<ArrayLike<T>>,
arr2: ArrayLike<T>,
): ArrayLike<T> | undefined {
if (!isArrayLike(arr1) || !isArrayLike(arr2)) {
return;
}
let newLength = arr1.length;
const len = +arr2.length;
for (let i = 0; i < len; i++) {
arr1[newLength++] = arr2[i];
}
arr1.length = newLength;
return arr1;
}
/**
* Checks if an object is array-like.
*
* @category Static
* @param item - Item to check.
* @returns Indicates if the item is array-like.
*/
function isArrayLike(item: unknown): item is ArrayLike<unknown> {
if (Array.isArray(item)) {
return true;
}
if (
typeof item !== 'object' ||
item === null ||
!('length' in item) ||
typeof item.length !== 'number' ||
item.length < 0
) {
return false;
}
for (let i = 0; i < item.length; i++) {
if (!(i in item)) {
return false;
}
}
return true;
}

58
backend/node_modules/cheerio/src/types.ts generated vendored Normal file
View File

@@ -0,0 +1,58 @@
/** @file Types used in signatures of Cheerio methods. */
type LowercaseLetters =
| 'a'
| 'b'
| 'c'
| 'd'
| 'e'
| 'f'
| 'g'
| 'h'
| 'i'
| 'j'
| 'k'
| 'l'
| 'm'
| 'n'
| 'o'
| 'p'
| 'q'
| 'r'
| 's'
| 't'
| 'u'
| 'v'
| 'w'
| 'x'
| 'y'
| 'z';
type AlphaNumeric =
| LowercaseLetters
| Uppercase<LowercaseLetters>
| `${number}`;
type SelectorSpecial = '.' | '#' | ':' | '|' | '>' | '+' | '~' | '[';
/**
* Type for identifying selectors. Allows us to "upgrade" queries using
* selectors to return `Element`s.
*/
export type SelectorType =
| `${SelectorSpecial}${AlphaNumeric}${string}`
| `${AlphaNumeric}${string}`;
import type { Cheerio } from './cheerio.js';
import type { AnyNode } from 'domhandler';
/** Elements that can be passed to manipulation methods. */
export type BasicAcceptedElems<T extends AnyNode> = ArrayLike<T> | T | string;
/** Elements that can be passed to manipulation methods, including functions. */
export type AcceptedElems<T extends AnyNode> =
| BasicAcceptedElems<T>
| ((this: T, i: number, el: T) => BasicAcceptedElems<T>);
/** Function signature, for traversal methods. */
export type FilterFunction<T> = (this: T, i: number, el: T) => boolean;
/** Supported filter types, for traversal methods. */
export type AcceptedFilters<T> = string | FilterFunction<T> | T | Cheerio<T>;

99
backend/node_modules/cheerio/src/utils.ts generated vendored Normal file
View File

@@ -0,0 +1,99 @@
import type { AnyNode } from 'domhandler';
import type { Cheerio } from './cheerio.js';
/**
* Checks if an object is a Cheerio instance.
*
* @category Utils
* @param maybeCheerio - The object to check.
* @returns Whether the object is a Cheerio instance.
*/
export function isCheerio<T>(
maybeCheerio: unknown,
): maybeCheerio is Cheerio<T> {
return (maybeCheerio as Cheerio<T>).cheerio != null;
}
/**
* Convert a string to camel case notation.
*
* @private
* @category Utils
* @param str - The string to be converted.
* @returns String in camel case notation.
*/
export function camelCase(str: string): string {
return str.replace(/[._-](\w|$)/g, (_, x) => (x as string).toUpperCase());
}
/**
* Convert a string from camel case to "CSS case", where word boundaries are
* described by hyphens ("-") and all characters are lower-case.
*
* @private
* @category Utils
* @param str - The string to be converted.
* @returns String in "CSS case".
*/
export function cssCase(str: string): string {
return str.replace(/[A-Z]/g, '-$&').toLowerCase();
}
/**
* Iterate over each DOM element without creating intermediary Cheerio
* instances.
*
* This is indented for use internally to avoid otherwise unnecessary memory
* pressure introduced by _make.
*
* @category Utils
* @param array - The array to iterate over.
* @param fn - Function to call.
* @returns The original instance.
*/
export function domEach<
T extends AnyNode,
Arr extends ArrayLike<T> = Cheerio<T>,
>(array: Arr, fn: (elem: T, index: number) => void): Arr {
const len = array.length;
for (let i = 0; i < len; i++) fn(array[i], i);
return array;
}
const enum CharacterCode {
LowerA = 97,
LowerZ = 122,
UpperA = 65,
UpperZ = 90,
Exclamation = 33,
}
/**
* Check if string is HTML.
*
* Tests for a `<` within a string, immediate followed by a letter and
* eventually followed by a `>`.
*
* @private
* @category Utils
* @param str - The string to check.
* @returns Indicates if `str` is HTML.
*/
export function isHtml(str: string): boolean {
if (typeof str !== 'string') {
return false;
}
const tagStart = str.indexOf('<');
if (tagStart === -1 || tagStart > str.length - 3) return false;
const tagChar = str.charCodeAt(tagStart + 1) as CharacterCode;
return (
((tagChar >= CharacterCode.LowerA && tagChar <= CharacterCode.LowerZ) ||
(tagChar >= CharacterCode.UpperA && tagChar <= CharacterCode.UpperZ) ||
tagChar === CharacterCode.Exclamation) &&
str.includes('>', tagStart + 2)
);
}