| /** | |
| * Methods for modifying the DOM structure. | |
| * | |
| * @module cheerio/manipulation | |
| */ | |
| import { | |
| isTag, | |
| Text, | |
| hasChildren, | |
| cloneNode, | |
| Document, | |
| type ParentNode, | |
| type AnyNode, | |
| type Element, | |
| } from 'domhandler'; | |
| import { update as updateDOM } from '../parse.js'; | |
| import { text as staticText } from '../static.js'; | |
| import { domEach, isHtml, isCheerio } from '../utils.js'; | |
| import { removeElement } from 'domutils'; | |
| import type { Cheerio } from '../cheerio.js'; | |
| import type { BasicAcceptedElems, AcceptedElems } from '../types.js'; | |
| import { ElementType } from 'htmlparser2'; | |
| /** | |
| * Create an array of nodes, recursing into arrays and parsing strings if | |
| * necessary. | |
| * | |
| * @private | |
| * @category Manipulation | |
| * @param elem - Elements to make an array of. | |
| * @param clone - Optionally clone nodes. | |
| * @returns The array of nodes. | |
| */ | |
| export function _makeDomArray<T extends AnyNode>( | |
| this: Cheerio<T>, | |
| elem?: BasicAcceptedElems<AnyNode> | BasicAcceptedElems<AnyNode>[], | |
| clone?: boolean, | |
| ): AnyNode[] { | |
| if (elem == null) { | |
| return []; | |
| } | |
| if (typeof elem === 'string') { | |
| return this._parse(elem, this.options, false, null).children.slice(0); | |
| } | |
| if ('length' in elem) { | |
| if (elem.length === 1) { | |
| return this._makeDomArray(elem[0], clone); | |
| } | |
| const result: AnyNode[] = []; | |
| for (let i = 0; i < elem.length; i++) { | |
| const el = elem[i]; | |
| if (typeof el === 'object') { | |
| if (el == null) { | |
| continue; | |
| } | |
| if (!('length' in el)) { | |
| result.push(clone ? cloneNode(el, true) : el); | |
| continue; | |
| } | |
| } | |
| result.push(...this._makeDomArray(el, clone)); | |
| } | |
| return result; | |
| } | |
| return [clone ? cloneNode(elem, true) : elem]; | |
| } | |
| function _insert( | |
| concatenator: ( | |
| dom: AnyNode[], | |
| children: AnyNode[], | |
| parent: ParentNode, | |
| ) => void, | |
| ) { | |
| return function <T extends AnyNode>( | |
| this: Cheerio<T>, | |
| ...elems: | |
| | [ | |
| ( | |
| this: AnyNode, | |
| i: number, | |
| html: string, | |
| ) => BasicAcceptedElems<AnyNode>, | |
| ] | |
| | BasicAcceptedElems<AnyNode>[] | |
| ) { | |
| const lastIdx = this.length - 1; | |
| return domEach(this, (el, i) => { | |
| if (!hasChildren(el)) return; | |
| const domSrc = | |
| typeof elems[0] === 'function' | |
| ? elems[0].call(el, i, this._render(el.children)) | |
| : (elems as BasicAcceptedElems<AnyNode>[]); | |
| const dom = this._makeDomArray(domSrc, i < lastIdx); | |
| concatenator(dom, el.children, el); | |
| }); | |
| }; | |
| } | |
| /** | |
| * Modify an array in-place, removing some number of elements and adding new | |
| * elements directly following them. | |
| * | |
| * @private | |
| * @category Manipulation | |
| * @param array - Target array to splice. | |
| * @param spliceIdx - Index at which to begin changing the array. | |
| * @param spliceCount - Number of elements to remove from the array. | |
| * @param newElems - Elements to insert into the array. | |
| * @param parent - The parent of the node. | |
| * @returns The spliced array. | |
| */ | |
| function uniqueSplice( | |
| array: AnyNode[], | |
| spliceIdx: number, | |
| spliceCount: number, | |
| newElems: AnyNode[], | |
| parent: ParentNode, | |
| ): AnyNode[] { | |
| const spliceArgs: Parameters<AnyNode[]['splice']> = [ | |
| spliceIdx, | |
| spliceCount, | |
| ...newElems, | |
| ]; | |
| const prev = spliceIdx === 0 ? null : array[spliceIdx - 1]; | |
| const next = | |
| spliceIdx + spliceCount >= array.length | |
| ? null | |
| : array[spliceIdx + spliceCount]; | |
| /* | |
| * Before splicing in new elements, ensure they do not already appear in the | |
| * current array. | |
| */ | |
| for (let idx = 0; idx < newElems.length; ++idx) { | |
| const node = newElems[idx]; | |
| const oldParent = node.parent; | |
| if (oldParent) { | |
| const oldSiblings: AnyNode[] = oldParent.children; | |
| const prevIdx = oldSiblings.indexOf(node); | |
| if (prevIdx !== -1) { | |
| oldParent.children.splice(prevIdx, 1); | |
| if (parent === oldParent && spliceIdx > prevIdx) { | |
| spliceArgs[0]--; | |
| } | |
| } | |
| } | |
| node.parent = parent; | |
| if (node.prev) { | |
| node.prev.next = node.next ?? null; | |
| } | |
| if (node.next) { | |
| node.next.prev = node.prev ?? null; | |
| } | |
| node.prev = idx === 0 ? prev : newElems[idx - 1]; | |
| node.next = idx === newElems.length - 1 ? next : newElems[idx + 1]; | |
| } | |
| if (prev) { | |
| prev.next = newElems[0]; | |
| } | |
| if (next) { | |
| next.prev = newElems[newElems.length - 1]; | |
| } | |
| return array.splice(...spliceArgs); | |
| } | |
| /** | |
| * Insert every element in the set of matched elements to the end of the target. | |
| * | |
| * @category Manipulation | |
| * @example | |
| * | |
| * ```js | |
| * $('<li class="plum">Plum</li>').appendTo('#fruits'); | |
| * $.html(); | |
| * //=> <ul id="fruits"> | |
| * // <li class="apple">Apple</li> | |
| * // <li class="orange">Orange</li> | |
| * // <li class="pear">Pear</li> | |
| * // <li class="plum">Plum</li> | |
| * // </ul> | |
| * ``` | |
| * | |
| * @param target - Element to append elements to. | |
| * @returns The instance itself. | |
| * @see {@link https://api.jquery.com/appendTo/} | |
| */ | |
| export function appendTo<T extends AnyNode>( | |
| this: Cheerio<T>, | |
| target: BasicAcceptedElems<AnyNode>, | |
| ): Cheerio<T> { | |
| const appendTarget = isCheerio<T>(target) ? target : this._make(target); | |
| appendTarget.append(this); | |
| return this; | |
| } | |
| /** | |
| * Insert every element in the set of matched elements to the beginning of the | |
| * target. | |
| * | |
| * @category Manipulation | |
| * @example | |
| * | |
| * ```js | |
| * $('<li class="plum">Plum</li>').prependTo('#fruits'); | |
| * $.html(); | |
| * //=> <ul id="fruits"> | |
| * // <li class="plum">Plum</li> | |
| * // <li class="apple">Apple</li> | |
| * // <li class="orange">Orange</li> | |
| * // <li class="pear">Pear</li> | |
| * // </ul> | |
| * ``` | |
| * | |
| * @param target - Element to prepend elements to. | |
| * @returns The instance itself. | |
| * @see {@link https://api.jquery.com/prependTo/} | |
| */ | |
| export function prependTo<T extends AnyNode>( | |
| this: Cheerio<T>, | |
| target: BasicAcceptedElems<AnyNode>, | |
| ): Cheerio<T> { | |
| const prependTarget = isCheerio<T>(target) ? target : this._make(target); | |
| prependTarget.prepend(this); | |
| return this; | |
| } | |
| /** | |
| * Inserts content as the _last_ child of each of the selected elements. | |
| * | |
| * @category Manipulation | |
| * @example | |
| * | |
| * ```js | |
| * $('ul').append('<li class="plum">Plum</li>'); | |
| * $.html(); | |
| * //=> <ul id="fruits"> | |
| * // <li class="apple">Apple</li> | |
| * // <li class="orange">Orange</li> | |
| * // <li class="pear">Pear</li> | |
| * // <li class="plum">Plum</li> | |
| * // </ul> | |
| * ``` | |
| * | |
| * @see {@link https://api.jquery.com/append/} | |
| */ | |
| export const append: <T extends AnyNode>( | |
| this: Cheerio<T>, | |
| ...elems: | |
| | [(this: AnyNode, i: number, html: string) => BasicAcceptedElems<AnyNode>] | |
| | BasicAcceptedElems<AnyNode>[] | |
| ) => Cheerio<T> = _insert((dom, children, parent) => { | |
| uniqueSplice(children, children.length, 0, dom, parent); | |
| }); | |
| /** | |
| * Inserts content as the _first_ child of each of the selected elements. | |
| * | |
| * @category Manipulation | |
| * @example | |
| * | |
| * ```js | |
| * $('ul').prepend('<li class="plum">Plum</li>'); | |
| * $.html(); | |
| * //=> <ul id="fruits"> | |
| * // <li class="plum">Plum</li> | |
| * // <li class="apple">Apple</li> | |
| * // <li class="orange">Orange</li> | |
| * // <li class="pear">Pear</li> | |
| * // </ul> | |
| * ``` | |
| * | |
| * @see {@link https://api.jquery.com/prepend/} | |
| */ | |
| export const prepend: <T extends AnyNode>( | |
| this: Cheerio<T>, | |
| ...elems: | |
| | [(this: AnyNode, i: number, html: string) => BasicAcceptedElems<AnyNode>] | |
| | BasicAcceptedElems<AnyNode>[] | |
| ) => Cheerio<T> = _insert((dom, children, parent) => { | |
| uniqueSplice(children, 0, 0, dom, parent); | |
| }); | |
| function _wrap( | |
| insert: ( | |
| el: AnyNode, | |
| elInsertLocation: ParentNode, | |
| wrapperDom: ParentNode[], | |
| ) => void, | |
| ) { | |
| return function <T extends AnyNode>( | |
| this: Cheerio<T>, | |
| wrapper: AcceptedElems<AnyNode>, | |
| ) { | |
| const lastIdx = this.length - 1; | |
| const lastParent = this.parents().last(); | |
| for (let i = 0; i < this.length; i++) { | |
| const el = this[i]; | |
| const wrap = | |
| typeof wrapper === 'function' | |
| ? wrapper.call(el, i, el) | |
| : typeof wrapper === 'string' && !isHtml(wrapper) | |
| ? lastParent.find(wrapper).clone() | |
| : wrapper; | |
| const [wrapperDom] = this._makeDomArray(wrap, i < lastIdx); | |
| if (!wrapperDom || !hasChildren(wrapperDom)) continue; | |
| let elInsertLocation = wrapperDom; | |
| /* | |
| * Find the deepest child. Only consider the first tag child of each node | |
| * (ignore text); stop if no children are found. | |
| */ | |
| let j = 0; | |
| while (j < elInsertLocation.children.length) { | |
| const child = elInsertLocation.children[j]; | |
| if (isTag(child)) { | |
| elInsertLocation = child; | |
| j = 0; | |
| } else { | |
| j++; | |
| } | |
| } | |
| insert(el, elInsertLocation, [wrapperDom]); | |
| } | |
| return this; | |
| }; | |
| } | |
| /** | |
| * The .wrap() function can take any string or object that could be passed to | |
| * the $() factory function to specify a DOM structure. This structure may be | |
| * nested several levels deep, but should contain only one inmost element. A | |
| * copy of this structure will be wrapped around each of the elements in the set | |
| * of matched elements. This method returns the original set of elements for | |
| * chaining purposes. | |
| * | |
| * @category Manipulation | |
| * @example | |
| * | |
| * ```js | |
| * const redFruit = $('<div class="red-fruit"></div>'); | |
| * $('.apple').wrap(redFruit); | |
| * | |
| * //=> <ul id="fruits"> | |
| * // <div class="red-fruit"> | |
| * // <li class="apple">Apple</li> | |
| * // </div> | |
| * // <li class="orange">Orange</li> | |
| * // <li class="plum">Plum</li> | |
| * // </ul> | |
| * | |
| * const healthy = $('<div class="healthy"></div>'); | |
| * $('li').wrap(healthy); | |
| * | |
| * //=> <ul id="fruits"> | |
| * // <div class="healthy"> | |
| * // <li class="apple">Apple</li> | |
| * // </div> | |
| * // <div class="healthy"> | |
| * // <li class="orange">Orange</li> | |
| * // </div> | |
| * // <div class="healthy"> | |
| * // <li class="plum">Plum</li> | |
| * // </div> | |
| * // </ul> | |
| * ``` | |
| * | |
| * @param wrapper - The DOM structure to wrap around each element in the | |
| * selection. | |
| * @see {@link https://api.jquery.com/wrap/} | |
| */ | |
| export const wrap: <T extends AnyNode>( | |
| this: Cheerio<T>, | |
| wrapper: AcceptedElems<AnyNode>, | |
| ) => Cheerio<T> = _wrap((el, elInsertLocation, wrapperDom) => { | |
| const { parent } = el; | |
| if (!parent) return; | |
| const siblings: AnyNode[] = parent.children; | |
| const index = siblings.indexOf(el); | |
| updateDOM([el], elInsertLocation); | |
| /* | |
| * The previous operation removed the current element from the `siblings` | |
| * array, so the `dom` array can be inserted without removing any | |
| * additional elements. | |
| */ | |
| uniqueSplice(siblings, index, 0, wrapperDom, parent); | |
| }); | |
| /** | |
| * The .wrapInner() function can take any string or object that could be passed | |
| * to the $() factory function to specify a DOM structure. This structure may be | |
| * nested several levels deep, but should contain only one inmost element. The | |
| * structure will be wrapped around the content of each of the elements in the | |
| * set of matched elements. | |
| * | |
| * @category Manipulation | |
| * @example | |
| * | |
| * ```js | |
| * const redFruit = $('<div class="red-fruit"></div>'); | |
| * $('.apple').wrapInner(redFruit); | |
| * | |
| * //=> <ul id="fruits"> | |
| * // <li class="apple"> | |
| * // <div class="red-fruit">Apple</div> | |
| * // </li> | |
| * // <li class="orange">Orange</li> | |
| * // <li class="pear">Pear</li> | |
| * // </ul> | |
| * | |
| * const healthy = $('<div class="healthy"></div>'); | |
| * $('li').wrapInner(healthy); | |
| * | |
| * //=> <ul id="fruits"> | |
| * // <li class="apple"> | |
| * // <div class="healthy">Apple</div> | |
| * // </li> | |
| * // <li class="orange"> | |
| * // <div class="healthy">Orange</div> | |
| * // </li> | |
| * // <li class="pear"> | |
| * // <div class="healthy">Pear</div> | |
| * // </li> | |
| * // </ul> | |
| * ``` | |
| * | |
| * @param wrapper - The DOM structure to wrap around the content of each element | |
| * in the selection. | |
| * @returns The instance itself, for chaining. | |
| * @see {@link https://api.jquery.com/wrapInner/} | |
| */ | |
| export const wrapInner: <T extends AnyNode>( | |
| this: Cheerio<T>, | |
| wrapper: AcceptedElems<AnyNode>, | |
| ) => Cheerio<T> = _wrap((el, elInsertLocation, wrapperDom) => { | |
| if (!hasChildren(el)) return; | |
| updateDOM(el.children, elInsertLocation); | |
| updateDOM(wrapperDom, el); | |
| }); | |
| /** | |
| * The .unwrap() function, removes the parents of the set of matched elements | |
| * from the DOM, leaving the matched elements in their place. | |
| * | |
| * @category Manipulation | |
| * @example <caption>without selector</caption> | |
| * | |
| * ```js | |
| * const $ = cheerio.load( | |
| * '<div id=test>\n <div><p>Hello</p></div>\n <div><p>World</p></div>\n</div>', | |
| * ); | |
| * $('#test p').unwrap(); | |
| * | |
| * //=> <div id=test> | |
| * // <p>Hello</p> | |
| * // <p>World</p> | |
| * // </div> | |
| * ``` | |
| * | |
| * @example <caption>with selector</caption> | |
| * | |
| * ```js | |
| * const $ = cheerio.load( | |
| * '<div id=test>\n <p>Hello</p>\n <b><p>World</p></b>\n</div>', | |
| * ); | |
| * $('#test p').unwrap('b'); | |
| * | |
| * //=> <div id=test> | |
| * // <p>Hello</p> | |
| * // <p>World</p> | |
| * // </div> | |
| * ``` | |
| * | |
| * @param selector - A selector to check the parent element against. If an | |
| * element's parent does not match the selector, the element won't be | |
| * unwrapped. | |
| * @returns The instance itself, for chaining. | |
| * @see {@link https://api.jquery.com/unwrap/} | |
| */ | |
| export function unwrap<T extends AnyNode>( | |
| this: Cheerio<T>, | |
| selector?: string, | |
| ): Cheerio<T> { | |
| this.parent(selector) | |
| .not('body') | |
| .each((_, el) => { | |
| this._make(el).replaceWith(el.children); | |
| }); | |
| return this; | |
| } | |
| /** | |
| * The .wrapAll() function can take any string or object that could be passed to | |
| * the $() function to specify a DOM structure. This structure may be nested | |
| * several levels deep, but should contain only one inmost element. The | |
| * structure will be wrapped around all of the elements in the set of matched | |
| * elements, as a single group. | |
| * | |
| * @category Manipulation | |
| * @example <caption>With markup passed to `wrapAll`</caption> | |
| * | |
| * ```js | |
| * const $ = cheerio.load( | |
| * '<div class="container"><div class="inner">First</div><div class="inner">Second</div></div>', | |
| * ); | |
| * $('.inner').wrapAll("<div class='new'></div>"); | |
| * | |
| * //=> <div class="container"> | |
| * // <div class='new'> | |
| * // <div class="inner">First</div> | |
| * // <div class="inner">Second</div> | |
| * // </div> | |
| * // </div> | |
| * ``` | |
| * | |
| * @example <caption>With an existing cheerio instance</caption> | |
| * | |
| * ```js | |
| * const $ = cheerio.load( | |
| * '<span>Span 1</span><strong>Strong</strong><span>Span 2</span>', | |
| * ); | |
| * const wrap = $('<div><p><em><b></b></em></p></div>'); | |
| * $('span').wrapAll(wrap); | |
| * | |
| * //=> <div> | |
| * // <p> | |
| * // <em> | |
| * // <b> | |
| * // <span>Span 1</span> | |
| * // <span>Span 2</span> | |
| * // </b> | |
| * // </em> | |
| * // </p> | |
| * // </div> | |
| * // <strong>Strong</strong> | |
| * ``` | |
| * | |
| * @param wrapper - The DOM structure to wrap around all matched elements in the | |
| * selection. | |
| * @returns The instance itself. | |
| * @see {@link https://api.jquery.com/wrapAll/} | |
| */ | |
| export function wrapAll<T extends AnyNode>( | |
| this: Cheerio<T>, | |
| wrapper: AcceptedElems<T>, | |
| ): Cheerio<T> { | |
| const el = this[0]; | |
| if (el) { | |
| const wrap: Cheerio<AnyNode> = this._make( | |
| typeof wrapper === 'function' ? wrapper.call(el, 0, el) : wrapper, | |
| ).insertBefore(el); | |
| // If html is given as wrapper, wrap may contain text elements | |
| let elInsertLocation: Element | undefined; | |
| for (let i = 0; i < wrap.length; i++) { | |
| if (wrap[i].type === ElementType.Tag) { | |
| elInsertLocation = wrap[i] as Element; | |
| } | |
| } | |
| let j = 0; | |
| /* | |
| * Find the deepest child. Only consider the first tag child of each node | |
| * (ignore text); stop if no children are found. | |
| */ | |
| while (elInsertLocation && j < elInsertLocation.children.length) { | |
| const child = elInsertLocation.children[j]; | |
| if (child.type === ElementType.Tag) { | |
| elInsertLocation = child; | |
| j = 0; | |
| } else { | |
| j++; | |
| } | |
| } | |
| if (elInsertLocation) this._make(elInsertLocation).append(this); | |
| } | |
| return this; | |
| } | |
| /** | |
| * Insert content next to each element in the set of matched elements. | |
| * | |
| * @category Manipulation | |
| * @example | |
| * | |
| * ```js | |
| * $('.apple').after('<li class="plum">Plum</li>'); | |
| * $.html(); | |
| * //=> <ul id="fruits"> | |
| * // <li class="apple">Apple</li> | |
| * // <li class="plum">Plum</li> | |
| * // <li class="orange">Orange</li> | |
| * // <li class="pear">Pear</li> | |
| * // </ul> | |
| * ``` | |
| * | |
| * @param elems - HTML string, DOM element, array of DOM elements or Cheerio to | |
| * insert after each element in the set of matched elements. | |
| * @returns The instance itself. | |
| * @see {@link https://api.jquery.com/after/} | |
| */ | |
| export function after<T extends AnyNode>( | |
| this: Cheerio<T>, | |
| ...elems: | |
| | [(this: AnyNode, i: number, html: string) => BasicAcceptedElems<AnyNode>] | |
| | BasicAcceptedElems<AnyNode>[] | |
| ): Cheerio<T> { | |
| const lastIdx = this.length - 1; | |
| return domEach(this, (el, i) => { | |
| if (!hasChildren(el) || !el.parent) { | |
| return; | |
| } | |
| const siblings: AnyNode[] = el.parent.children; | |
| const index = siblings.indexOf(el); | |
| // If not found, move on | |
| /* istanbul ignore next */ | |
| if (index === -1) return; | |
| const domSrc = | |
| typeof elems[0] === 'function' | |
| ? elems[0].call(el, i, this._render(el.children)) | |
| : (elems as BasicAcceptedElems<AnyNode>[]); | |
| const dom = this._makeDomArray(domSrc, i < lastIdx); | |
| // Add element after `this` element | |
| uniqueSplice(siblings, index + 1, 0, dom, el.parent); | |
| }); | |
| } | |
| /** | |
| * Insert every element in the set of matched elements after the target. | |
| * | |
| * @category Manipulation | |
| * @example | |
| * | |
| * ```js | |
| * $('<li class="plum">Plum</li>').insertAfter('.apple'); | |
| * $.html(); | |
| * //=> <ul id="fruits"> | |
| * // <li class="apple">Apple</li> | |
| * // <li class="plum">Plum</li> | |
| * // <li class="orange">Orange</li> | |
| * // <li class="pear">Pear</li> | |
| * // </ul> | |
| * ``` | |
| * | |
| * @param target - Element to insert elements after. | |
| * @returns The set of newly inserted elements. | |
| * @see {@link https://api.jquery.com/insertAfter/} | |
| */ | |
| export function insertAfter<T extends AnyNode>( | |
| this: Cheerio<T>, | |
| target: BasicAcceptedElems<AnyNode>, | |
| ): Cheerio<T> { | |
| if (typeof target === 'string') { | |
| target = this._make<AnyNode>(target); | |
| } | |
| this.remove(); | |
| const clones: T[] = []; | |
| for (const el of this._makeDomArray(target)) { | |
| const clonedSelf = this.clone().toArray(); | |
| const { parent } = el; | |
| if (!parent) { | |
| continue; | |
| } | |
| const siblings: AnyNode[] = parent.children; | |
| const index = siblings.indexOf(el); | |
| // If not found, move on | |
| /* istanbul ignore next */ | |
| if (index === -1) continue; | |
| // Add cloned `this` element(s) after target element | |
| uniqueSplice(siblings, index + 1, 0, clonedSelf, parent); | |
| clones.push(...clonedSelf); | |
| } | |
| return this._make(clones); | |
| } | |
| /** | |
| * Insert content previous to each element in the set of matched elements. | |
| * | |
| * @category Manipulation | |
| * @example | |
| * | |
| * ```js | |
| * $('.apple').before('<li class="plum">Plum</li>'); | |
| * $.html(); | |
| * //=> <ul id="fruits"> | |
| * // <li class="plum">Plum</li> | |
| * // <li class="apple">Apple</li> | |
| * // <li class="orange">Orange</li> | |
| * // <li class="pear">Pear</li> | |
| * // </ul> | |
| * ``` | |
| * | |
| * @param elems - HTML string, DOM element, array of DOM elements or Cheerio to | |
| * insert before each element in the set of matched elements. | |
| * @returns The instance itself. | |
| * @see {@link https://api.jquery.com/before/} | |
| */ | |
| export function before<T extends AnyNode>( | |
| this: Cheerio<T>, | |
| ...elems: | |
| | [(this: AnyNode, i: number, html: string) => BasicAcceptedElems<AnyNode>] | |
| | BasicAcceptedElems<AnyNode>[] | |
| ): Cheerio<T> { | |
| const lastIdx = this.length - 1; | |
| return domEach(this, (el, i) => { | |
| if (!hasChildren(el) || !el.parent) { | |
| return; | |
| } | |
| const siblings: AnyNode[] = el.parent.children; | |
| const index = siblings.indexOf(el); | |
| // If not found, move on | |
| /* istanbul ignore next */ | |
| if (index === -1) return; | |
| const domSrc = | |
| typeof elems[0] === 'function' | |
| ? elems[0].call(el, i, this._render(el.children)) | |
| : (elems as BasicAcceptedElems<AnyNode>[]); | |
| const dom = this._makeDomArray(domSrc, i < lastIdx); | |
| // Add element before `el` element | |
| uniqueSplice(siblings, index, 0, dom, el.parent); | |
| }); | |
| } | |
| /** | |
| * Insert every element in the set of matched elements before the target. | |
| * | |
| * @category Manipulation | |
| * @example | |
| * | |
| * ```js | |
| * $('<li class="plum">Plum</li>').insertBefore('.apple'); | |
| * $.html(); | |
| * //=> <ul id="fruits"> | |
| * // <li class="plum">Plum</li> | |
| * // <li class="apple">Apple</li> | |
| * // <li class="orange">Orange</li> | |
| * // <li class="pear">Pear</li> | |
| * // </ul> | |
| * ``` | |
| * | |
| * @param target - Element to insert elements before. | |
| * @returns The set of newly inserted elements. | |
| * @see {@link https://api.jquery.com/insertBefore/} | |
| */ | |
| export function insertBefore<T extends AnyNode>( | |
| this: Cheerio<T>, | |
| target: BasicAcceptedElems<AnyNode>, | |
| ): Cheerio<T> { | |
| const targetArr = this._make<AnyNode>(target); | |
| this.remove(); | |
| const clones: T[] = []; | |
| domEach(targetArr, (el) => { | |
| const clonedSelf = this.clone().toArray(); | |
| const { parent } = el; | |
| if (!parent) { | |
| return; | |
| } | |
| const siblings: AnyNode[] = parent.children; | |
| const index = siblings.indexOf(el); | |
| // If not found, move on | |
| /* istanbul ignore next */ | |
| if (index === -1) return; | |
| // Add cloned `this` element(s) after target element | |
| uniqueSplice(siblings, index, 0, clonedSelf, parent); | |
| clones.push(...clonedSelf); | |
| }); | |
| return this._make(clones); | |
| } | |
| /** | |
| * Removes the set of matched elements from the DOM and all their children. | |
| * `selector` filters the set of matched elements to be removed. | |
| * | |
| * @category Manipulation | |
| * @example | |
| * | |
| * ```js | |
| * $('.pear').remove(); | |
| * $.html(); | |
| * //=> <ul id="fruits"> | |
| * // <li class="apple">Apple</li> | |
| * // <li class="orange">Orange</li> | |
| * // </ul> | |
| * ``` | |
| * | |
| * @param selector - Optional selector for elements to remove. | |
| * @returns The instance itself. | |
| * @see {@link https://api.jquery.com/remove/} | |
| */ | |
| export function remove<T extends AnyNode>( | |
| this: Cheerio<T>, | |
| selector?: string, | |
| ): Cheerio<T> { | |
| // Filter if we have selector | |
| const elems = selector ? this.filter(selector) : this; | |
| domEach(elems, (el) => { | |
| removeElement(el); | |
| el.prev = el.next = el.parent = null; | |
| }); | |
| return this; | |
| } | |
| /** | |
| * Replaces matched elements with `content`. | |
| * | |
| * @category Manipulation | |
| * @example | |
| * | |
| * ```js | |
| * const plum = $('<li class="plum">Plum</li>'); | |
| * $('.pear').replaceWith(plum); | |
| * $.html(); | |
| * //=> <ul id="fruits"> | |
| * // <li class="apple">Apple</li> | |
| * // <li class="orange">Orange</li> | |
| * // <li class="plum">Plum</li> | |
| * // </ul> | |
| * ``` | |
| * | |
| * @param content - Replacement for matched elements. | |
| * @returns The instance itself. | |
| * @see {@link https://api.jquery.com/replaceWith/} | |
| */ | |
| export function replaceWith<T extends AnyNode>( | |
| this: Cheerio<T>, | |
| content: AcceptedElems<AnyNode>, | |
| ): Cheerio<T> { | |
| return domEach(this, (el, i) => { | |
| const { parent } = el; | |
| if (!parent) { | |
| return; | |
| } | |
| const siblings: AnyNode[] = parent.children; | |
| const cont = | |
| typeof content === 'function' ? content.call(el, i, el) : content; | |
| const dom = this._makeDomArray(cont); | |
| /* | |
| * In the case that `dom` contains nodes that already exist in other | |
| * structures, ensure those nodes are properly removed. | |
| */ | |
| updateDOM(dom, null); | |
| const index = siblings.indexOf(el); | |
| // Completely remove old element | |
| uniqueSplice(siblings, index, 1, dom, parent); | |
| if (!dom.includes(el)) { | |
| el.parent = el.prev = el.next = null; | |
| } | |
| }); | |
| } | |
| /** | |
| * Removes all children from each item in the selection. Text nodes and comment | |
| * nodes are left as is. | |
| * | |
| * @category Manipulation | |
| * @example | |
| * | |
| * ```js | |
| * $('ul').empty(); | |
| * $.html(); | |
| * //=> <ul id="fruits"></ul> | |
| * ``` | |
| * | |
| * @returns The instance itself. | |
| * @see {@link https://api.jquery.com/empty/} | |
| */ | |
| export function empty<T extends AnyNode>(this: Cheerio<T>): Cheerio<T> { | |
| return domEach(this, (el) => { | |
| if (!hasChildren(el)) return; | |
| for (const child of el.children) { | |
| child.next = child.prev = child.parent = null; | |
| } | |
| el.children.length = 0; | |
| }); | |
| } | |
| /** | |
| * Gets an HTML content string from the first selected element. | |
| * | |
| * @category Manipulation | |
| * @example | |
| * | |
| * ```js | |
| * $('.orange').html(); | |
| * //=> Orange | |
| * | |
| * $('#fruits').html('<li class="mango">Mango</li>').html(); | |
| * //=> <li class="mango">Mango</li> | |
| * ``` | |
| * | |
| * @returns The HTML content string. | |
| * @see {@link https://api.jquery.com/html/} | |
| */ | |
| export function html<T extends AnyNode>(this: Cheerio<T>): string | null; | |
| /** | |
| * Replaces each selected element's content with the specified content. | |
| * | |
| * @category Manipulation | |
| * @example | |
| * | |
| * ```js | |
| * $('.orange').html('<li class="mango">Mango</li>').html(); | |
| * //=> <li class="mango">Mango</li> | |
| * ``` | |
| * | |
| * @param str - The content to replace selection's contents with. | |
| * @returns The instance itself. | |
| * @see {@link https://api.jquery.com/html/} | |
| */ | |
| export function html<T extends AnyNode>( | |
| this: Cheerio<T>, | |
| str: string | Cheerio<T>, | |
| ): Cheerio<T>; | |
| export function html<T extends AnyNode>( | |
| this: Cheerio<T>, | |
| str?: string | Cheerio<AnyNode>, | |
| ): Cheerio<T> | string | null { | |
| if (str === undefined) { | |
| const el = this[0]; | |
| if (!el || !hasChildren(el)) return null; | |
| return this._render(el.children); | |
| } | |
| return domEach(this, (el) => { | |
| if (!hasChildren(el)) return; | |
| for (const child of el.children) { | |
| child.next = child.prev = child.parent = null; | |
| } | |
| const content = isCheerio(str) | |
| ? str.toArray() | |
| : this._parse(`${str}`, this.options, false, el).children; | |
| updateDOM(content, el); | |
| }); | |
| } | |
| /** | |
| * Turns the collection to a string. Alias for `.html()`. | |
| * | |
| * @category Manipulation | |
| * @returns The rendered document. | |
| */ | |
| export function toString<T extends AnyNode>(this: Cheerio<T>): string { | |
| return this._render(this); | |
| } | |
| /** | |
| * Get the combined text contents of each element in the set of matched | |
| * elements, including their descendants. | |
| * | |
| * @category Manipulation | |
| * @example | |
| * | |
| * ```js | |
| * $('.orange').text(); | |
| * //=> Orange | |
| * | |
| * $('ul').text(); | |
| * //=> Apple | |
| * // Orange | |
| * // Pear | |
| * ``` | |
| * | |
| * @returns The text contents of the collection. | |
| * @see {@link https://api.jquery.com/text/} | |
| */ | |
| export function text<T extends AnyNode>(this: Cheerio<T>): string; | |
| /** | |
| * Set the content of each element in the set of matched elements to the | |
| * specified text. | |
| * | |
| * @category Manipulation | |
| * @example | |
| * | |
| * ```js | |
| * $('.orange').text('Orange'); | |
| * //=> <div class="orange">Orange</div> | |
| * ``` | |
| * | |
| * @param str - The text to set as the content of each matched element. | |
| * @returns The instance itself. | |
| * @see {@link https://api.jquery.com/text/} | |
| */ | |
| export function text<T extends AnyNode>( | |
| this: Cheerio<T>, | |
| str: string | ((this: AnyNode, i: number, text: string) => string), | |
| ): Cheerio<T>; | |
| export function text<T extends AnyNode>( | |
| this: Cheerio<T>, | |
| str?: string | ((this: AnyNode, i: number, text: string) => string), | |
| ): Cheerio<T> | string { | |
| // If `str` is undefined, act as a "getter" | |
| if (str === undefined) { | |
| return staticText(this); | |
| } | |
| if (typeof str === 'function') { | |
| // Function support | |
| return domEach(this, (el, i) => | |
| this._make(el).text(str.call(el, i, staticText([el]))), | |
| ); | |
| } | |
| // Append text node to each selected elements | |
| return domEach(this, (el) => { | |
| if (!hasChildren(el)) return; | |
| for (const child of el.children) { | |
| child.next = child.prev = child.parent = null; | |
| } | |
| const textNode = new Text(`${str}`); | |
| updateDOM(textNode, el); | |
| }); | |
| } | |
| /** | |
| * Clone the cheerio object. | |
| * | |
| * @category Manipulation | |
| * @example | |
| * | |
| * ```js | |
| * const moreFruit = $('#fruits').clone(); | |
| * ``` | |
| * | |
| * @returns The cloned object. | |
| * @see {@link https://api.jquery.com/clone/} | |
| */ | |
| export function clone<T extends AnyNode>(this: Cheerio<T>): Cheerio<T> { | |
| const clone = Array.prototype.map.call( | |
| this.get(), | |
| (el) => cloneNode(el, true) as T, | |
| ) as T[]; | |
| // Add a root node around the cloned nodes | |
| const root = new Document(clone); | |
| for (const node of clone) { | |
| node.parent = root; | |
| } | |
| return this._make(clone); | |
| } | |