manipulation.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810
  1. /**
  2. * Methods for modifying the DOM structure.
  3. *
  4. * @module cheerio/manipulation
  5. */
  6. import { Text, hasChildren } from 'domhandler';
  7. import { update as updateDOM } from '../parse.js';
  8. import { text as staticText } from '../static.js';
  9. import { domEach, cloneDom, isTag, isHtml, isCheerio } from '../utils.js';
  10. import { removeElement } from 'domutils';
  11. /**
  12. * Create an array of nodes, recursing into arrays and parsing strings if necessary.
  13. *
  14. * @private
  15. * @category Manipulation
  16. * @param elem - Elements to make an array of.
  17. * @param clone - Optionally clone nodes.
  18. * @returns The array of nodes.
  19. */
  20. export function _makeDomArray(elem, clone) {
  21. if (elem == null) {
  22. return [];
  23. }
  24. if (isCheerio(elem)) {
  25. return clone ? cloneDom(elem.get()) : elem.get();
  26. }
  27. if (Array.isArray(elem)) {
  28. return elem.reduce((newElems, el) => newElems.concat(this._makeDomArray(el, clone)), []);
  29. }
  30. if (typeof elem === 'string') {
  31. return this._parse(elem, this.options, false, null).children;
  32. }
  33. return clone ? cloneDom([elem]) : [elem];
  34. }
  35. function _insert(concatenator) {
  36. return function (...elems) {
  37. const lastIdx = this.length - 1;
  38. return domEach(this, (el, i) => {
  39. if (!hasChildren(el))
  40. return;
  41. const domSrc = typeof elems[0] === 'function'
  42. ? elems[0].call(el, i, this._render(el.children))
  43. : elems;
  44. const dom = this._makeDomArray(domSrc, i < lastIdx);
  45. concatenator(dom, el.children, el);
  46. });
  47. };
  48. }
  49. /**
  50. * Modify an array in-place, removing some number of elements and adding new
  51. * elements directly following them.
  52. *
  53. * @private
  54. * @category Manipulation
  55. * @param array - Target array to splice.
  56. * @param spliceIdx - Index at which to begin changing the array.
  57. * @param spliceCount - Number of elements to remove from the array.
  58. * @param newElems - Elements to insert into the array.
  59. * @param parent - The parent of the node.
  60. * @returns The spliced array.
  61. */
  62. function uniqueSplice(array, spliceIdx, spliceCount, newElems, parent) {
  63. var _a, _b;
  64. const spliceArgs = [
  65. spliceIdx,
  66. spliceCount,
  67. ...newElems,
  68. ];
  69. const prev = spliceIdx === 0 ? null : array[spliceIdx - 1];
  70. const next = spliceIdx + spliceCount >= array.length
  71. ? null
  72. : array[spliceIdx + spliceCount];
  73. /*
  74. * Before splicing in new elements, ensure they do not already appear in the
  75. * current array.
  76. */
  77. for (let idx = 0; idx < newElems.length; ++idx) {
  78. const node = newElems[idx];
  79. const oldParent = node.parent;
  80. if (oldParent) {
  81. const oldSiblings = oldParent.children;
  82. const prevIdx = oldSiblings.indexOf(node);
  83. if (prevIdx > -1) {
  84. oldParent.children.splice(prevIdx, 1);
  85. if (parent === oldParent && spliceIdx > prevIdx) {
  86. spliceArgs[0]--;
  87. }
  88. }
  89. }
  90. node.parent = parent;
  91. if (node.prev) {
  92. node.prev.next = (_a = node.next) !== null && _a !== void 0 ? _a : null;
  93. }
  94. if (node.next) {
  95. node.next.prev = (_b = node.prev) !== null && _b !== void 0 ? _b : null;
  96. }
  97. node.prev = idx === 0 ? prev : newElems[idx - 1];
  98. node.next = idx === newElems.length - 1 ? next : newElems[idx + 1];
  99. }
  100. if (prev) {
  101. prev.next = newElems[0];
  102. }
  103. if (next) {
  104. next.prev = newElems[newElems.length - 1];
  105. }
  106. return array.splice(...spliceArgs);
  107. }
  108. /**
  109. * Insert every element in the set of matched elements to the end of the target.
  110. *
  111. * @category Manipulation
  112. * @example
  113. *
  114. * ```js
  115. * $('<li class="plum">Plum</li>').appendTo('#fruits');
  116. * $.html();
  117. * //=> <ul id="fruits">
  118. * // <li class="apple">Apple</li>
  119. * // <li class="orange">Orange</li>
  120. * // <li class="pear">Pear</li>
  121. * // <li class="plum">Plum</li>
  122. * // </ul>
  123. * ```
  124. *
  125. * @param target - Element to append elements to.
  126. * @returns The instance itself.
  127. * @see {@link https://api.jquery.com/appendTo/}
  128. */
  129. export function appendTo(target) {
  130. const appendTarget = isCheerio(target) ? target : this._make(target);
  131. appendTarget.append(this);
  132. return this;
  133. }
  134. /**
  135. * Insert every element in the set of matched elements to the beginning of the target.
  136. *
  137. * @category Manipulation
  138. * @example
  139. *
  140. * ```js
  141. * $('<li class="plum">Plum</li>').prependTo('#fruits');
  142. * $.html();
  143. * //=> <ul id="fruits">
  144. * // <li class="plum">Plum</li>
  145. * // <li class="apple">Apple</li>
  146. * // <li class="orange">Orange</li>
  147. * // <li class="pear">Pear</li>
  148. * // </ul>
  149. * ```
  150. *
  151. * @param target - Element to prepend elements to.
  152. * @returns The instance itself.
  153. * @see {@link https://api.jquery.com/prependTo/}
  154. */
  155. export function prependTo(target) {
  156. const prependTarget = isCheerio(target) ? target : this._make(target);
  157. prependTarget.prepend(this);
  158. return this;
  159. }
  160. /**
  161. * Inserts content as the _last_ child of each of the selected elements.
  162. *
  163. * @category Manipulation
  164. * @example
  165. *
  166. * ```js
  167. * $('ul').append('<li class="plum">Plum</li>');
  168. * $.html();
  169. * //=> <ul id="fruits">
  170. * // <li class="apple">Apple</li>
  171. * // <li class="orange">Orange</li>
  172. * // <li class="pear">Pear</li>
  173. * // <li class="plum">Plum</li>
  174. * // </ul>
  175. * ```
  176. *
  177. * @see {@link https://api.jquery.com/append/}
  178. */
  179. export const append = _insert((dom, children, parent) => {
  180. uniqueSplice(children, children.length, 0, dom, parent);
  181. });
  182. /**
  183. * Inserts content as the _first_ child of each of the selected elements.
  184. *
  185. * @category Manipulation
  186. * @example
  187. *
  188. * ```js
  189. * $('ul').prepend('<li class="plum">Plum</li>');
  190. * $.html();
  191. * //=> <ul id="fruits">
  192. * // <li class="plum">Plum</li>
  193. * // <li class="apple">Apple</li>
  194. * // <li class="orange">Orange</li>
  195. * // <li class="pear">Pear</li>
  196. * // </ul>
  197. * ```
  198. *
  199. * @see {@link https://api.jquery.com/prepend/}
  200. */
  201. export const prepend = _insert((dom, children, parent) => {
  202. uniqueSplice(children, 0, 0, dom, parent);
  203. });
  204. function _wrap(insert) {
  205. return function (wrapper) {
  206. const lastIdx = this.length - 1;
  207. const lastParent = this.parents().last();
  208. for (let i = 0; i < this.length; i++) {
  209. const el = this[i];
  210. const wrap = typeof wrapper === 'function'
  211. ? wrapper.call(el, i, el)
  212. : typeof wrapper === 'string' && !isHtml(wrapper)
  213. ? lastParent.find(wrapper).clone()
  214. : wrapper;
  215. const [wrapperDom] = this._makeDomArray(wrap, i < lastIdx);
  216. if (!wrapperDom || !hasChildren(wrapperDom))
  217. continue;
  218. let elInsertLocation = wrapperDom;
  219. /*
  220. * Find the deepest child. Only consider the first tag child of each node
  221. * (ignore text); stop if no children are found.
  222. */
  223. let j = 0;
  224. while (j < elInsertLocation.children.length) {
  225. const child = elInsertLocation.children[j];
  226. if (isTag(child)) {
  227. elInsertLocation = child;
  228. j = 0;
  229. }
  230. else {
  231. j++;
  232. }
  233. }
  234. insert(el, elInsertLocation, [wrapperDom]);
  235. }
  236. return this;
  237. };
  238. }
  239. /**
  240. * The .wrap() function can take any string or object that could be passed to
  241. * the $() factory function to specify a DOM structure. This structure may be
  242. * nested several levels deep, but should contain only one inmost element. A
  243. * copy of this structure will be wrapped around each of the elements in the set
  244. * of matched elements. This method returns the original set of elements for
  245. * chaining purposes.
  246. *
  247. * @category Manipulation
  248. * @example
  249. *
  250. * ```js
  251. * const redFruit = $('<div class="red-fruit"></div>');
  252. * $('.apple').wrap(redFruit);
  253. *
  254. * //=> <ul id="fruits">
  255. * // <div class="red-fruit">
  256. * // <li class="apple">Apple</li>
  257. * // </div>
  258. * // <li class="orange">Orange</li>
  259. * // <li class="plum">Plum</li>
  260. * // </ul>
  261. *
  262. * const healthy = $('<div class="healthy"></div>');
  263. * $('li').wrap(healthy);
  264. *
  265. * //=> <ul id="fruits">
  266. * // <div class="healthy">
  267. * // <li class="apple">Apple</li>
  268. * // </div>
  269. * // <div class="healthy">
  270. * // <li class="orange">Orange</li>
  271. * // </div>
  272. * // <div class="healthy">
  273. * // <li class="plum">Plum</li>
  274. * // </div>
  275. * // </ul>
  276. * ```
  277. *
  278. * @param wrapper - The DOM structure to wrap around each element in the selection.
  279. * @see {@link https://api.jquery.com/wrap/}
  280. */
  281. export const wrap = _wrap((el, elInsertLocation, wrapperDom) => {
  282. const { parent } = el;
  283. if (!parent)
  284. return;
  285. const siblings = parent.children;
  286. const index = siblings.indexOf(el);
  287. updateDOM([el], elInsertLocation);
  288. /*
  289. * The previous operation removed the current element from the `siblings`
  290. * array, so the `dom` array can be inserted without removing any
  291. * additional elements.
  292. */
  293. uniqueSplice(siblings, index, 0, wrapperDom, parent);
  294. });
  295. /**
  296. * The .wrapInner() function can take any string or object that could be passed
  297. * to the $() factory function to specify a DOM structure. This structure may be
  298. * nested several levels deep, but should contain only one inmost element. The
  299. * structure will be wrapped around the content of each of the elements in the
  300. * set of matched elements.
  301. *
  302. * @category Manipulation
  303. * @example
  304. *
  305. * ```js
  306. * const redFruit = $('<div class="red-fruit"></div>');
  307. * $('.apple').wrapInner(redFruit);
  308. *
  309. * //=> <ul id="fruits">
  310. * // <li class="apple">
  311. * // <div class="red-fruit">Apple</div>
  312. * // </li>
  313. * // <li class="orange">Orange</li>
  314. * // <li class="pear">Pear</li>
  315. * // </ul>
  316. *
  317. * const healthy = $('<div class="healthy"></div>');
  318. * $('li').wrapInner(healthy);
  319. *
  320. * //=> <ul id="fruits">
  321. * // <li class="apple">
  322. * // <div class="healthy">Apple</div>
  323. * // </li>
  324. * // <li class="orange">
  325. * // <div class="healthy">Orange</div>
  326. * // </li>
  327. * // <li class="pear">
  328. * // <div class="healthy">Pear</div>
  329. * // </li>
  330. * // </ul>
  331. * ```
  332. *
  333. * @param wrapper - The DOM structure to wrap around the content of each element
  334. * in the selection.
  335. * @returns The instance itself, for chaining.
  336. * @see {@link https://api.jquery.com/wrapInner/}
  337. */
  338. export const wrapInner = _wrap((el, elInsertLocation, wrapperDom) => {
  339. if (!hasChildren(el))
  340. return;
  341. updateDOM(el.children, elInsertLocation);
  342. updateDOM(wrapperDom, el);
  343. });
  344. /**
  345. * The .unwrap() function, removes the parents of the set of matched elements
  346. * from the DOM, leaving the matched elements in their place.
  347. *
  348. * @category Manipulation
  349. * @example <caption>without selector</caption>
  350. *
  351. * ```js
  352. * const $ = cheerio.load(
  353. * '<div id=test>\n <div><p>Hello</p></div>\n <div><p>World</p></div>\n</div>'
  354. * );
  355. * $('#test p').unwrap();
  356. *
  357. * //=> <div id=test>
  358. * // <p>Hello</p>
  359. * // <p>World</p>
  360. * // </div>
  361. * ```
  362. *
  363. * @example <caption>with selector</caption>
  364. *
  365. * ```js
  366. * const $ = cheerio.load(
  367. * '<div id=test>\n <p>Hello</p>\n <b><p>World</p></b>\n</div>'
  368. * );
  369. * $('#test p').unwrap('b');
  370. *
  371. * //=> <div id=test>
  372. * // <p>Hello</p>
  373. * // <p>World</p>
  374. * // </div>
  375. * ```
  376. *
  377. * @param selector - A selector to check the parent element against. If an
  378. * element's parent does not match the selector, the element won't be unwrapped.
  379. * @returns The instance itself, for chaining.
  380. * @see {@link https://api.jquery.com/unwrap/}
  381. */
  382. export function unwrap(selector) {
  383. this.parent(selector)
  384. .not('body')
  385. .each((_, el) => {
  386. this._make(el).replaceWith(el.children);
  387. });
  388. return this;
  389. }
  390. /**
  391. * The .wrapAll() function can take any string or object that could be passed to
  392. * the $() function to specify a DOM structure. This structure may be nested
  393. * several levels deep, but should contain only one inmost element. The
  394. * structure will be wrapped around all of the elements in the set of matched
  395. * elements, as a single group.
  396. *
  397. * @category Manipulation
  398. * @example <caption>With markup passed to `wrapAll`</caption>
  399. *
  400. * ```js
  401. * const $ = cheerio.load(
  402. * '<div class="container"><div class="inner">First</div><div class="inner">Second</div></div>'
  403. * );
  404. * $('.inner').wrapAll("<div class='new'></div>");
  405. *
  406. * //=> <div class="container">
  407. * // <div class='new'>
  408. * // <div class="inner">First</div>
  409. * // <div class="inner">Second</div>
  410. * // </div>
  411. * // </div>
  412. * ```
  413. *
  414. * @example <caption>With an existing cheerio instance</caption>
  415. *
  416. * ```js
  417. * const $ = cheerio.load(
  418. * '<span>Span 1</span><strong>Strong</strong><span>Span 2</span>'
  419. * );
  420. * const wrap = $('<div><p><em><b></b></em></p></div>');
  421. * $('span').wrapAll(wrap);
  422. *
  423. * //=> <div>
  424. * // <p>
  425. * // <em>
  426. * // <b>
  427. * // <span>Span 1</span>
  428. * // <span>Span 2</span>
  429. * // </b>
  430. * // </em>
  431. * // </p>
  432. * // </div>
  433. * // <strong>Strong</strong>
  434. * ```
  435. *
  436. * @param wrapper - The DOM structure to wrap around all matched elements in the
  437. * selection.
  438. * @returns The instance itself.
  439. * @see {@link https://api.jquery.com/wrapAll/}
  440. */
  441. export function wrapAll(wrapper) {
  442. const el = this[0];
  443. if (el) {
  444. const wrap = this._make(typeof wrapper === 'function' ? wrapper.call(el, 0, el) : wrapper).insertBefore(el);
  445. // If html is given as wrapper, wrap may contain text elements
  446. let elInsertLocation;
  447. for (let i = 0; i < wrap.length; i++) {
  448. if (wrap[i].type === 'tag')
  449. elInsertLocation = wrap[i];
  450. }
  451. let j = 0;
  452. /*
  453. * Find the deepest child. Only consider the first tag child of each node
  454. * (ignore text); stop if no children are found.
  455. */
  456. while (elInsertLocation && j < elInsertLocation.children.length) {
  457. const child = elInsertLocation.children[j];
  458. if (child.type === 'tag') {
  459. elInsertLocation = child;
  460. j = 0;
  461. }
  462. else {
  463. j++;
  464. }
  465. }
  466. if (elInsertLocation)
  467. this._make(elInsertLocation).append(this);
  468. }
  469. return this;
  470. }
  471. /* eslint-disable jsdoc/check-param-names*/
  472. /**
  473. * Insert content next to each element in the set of matched elements.
  474. *
  475. * @category Manipulation
  476. * @example
  477. *
  478. * ```js
  479. * $('.apple').after('<li class="plum">Plum</li>');
  480. * $.html();
  481. * //=> <ul id="fruits">
  482. * // <li class="apple">Apple</li>
  483. * // <li class="plum">Plum</li>
  484. * // <li class="orange">Orange</li>
  485. * // <li class="pear">Pear</li>
  486. * // </ul>
  487. * ```
  488. *
  489. * @param content - HTML string, DOM element, array of DOM elements or Cheerio
  490. * to insert after each element in the set of matched elements.
  491. * @returns The instance itself.
  492. * @see {@link https://api.jquery.com/after/}
  493. */
  494. export function after(...elems) {
  495. const lastIdx = this.length - 1;
  496. return domEach(this, (el, i) => {
  497. const { parent } = el;
  498. if (!hasChildren(el) || !parent) {
  499. return;
  500. }
  501. const siblings = parent.children;
  502. const index = siblings.indexOf(el);
  503. // If not found, move on
  504. /* istanbul ignore next */
  505. if (index < 0)
  506. return;
  507. const domSrc = typeof elems[0] === 'function'
  508. ? elems[0].call(el, i, this._render(el.children))
  509. : elems;
  510. const dom = this._makeDomArray(domSrc, i < lastIdx);
  511. // Add element after `this` element
  512. uniqueSplice(siblings, index + 1, 0, dom, parent);
  513. });
  514. }
  515. /* eslint-enable jsdoc/check-param-names*/
  516. /**
  517. * Insert every element in the set of matched elements after the target.
  518. *
  519. * @category Manipulation
  520. * @example
  521. *
  522. * ```js
  523. * $('<li class="plum">Plum</li>').insertAfter('.apple');
  524. * $.html();
  525. * //=> <ul id="fruits">
  526. * // <li class="apple">Apple</li>
  527. * // <li class="plum">Plum</li>
  528. * // <li class="orange">Orange</li>
  529. * // <li class="pear">Pear</li>
  530. * // </ul>
  531. * ```
  532. *
  533. * @param target - Element to insert elements after.
  534. * @returns The set of newly inserted elements.
  535. * @see {@link https://api.jquery.com/insertAfter/}
  536. */
  537. export function insertAfter(target) {
  538. if (typeof target === 'string') {
  539. target = this._make(target);
  540. }
  541. this.remove();
  542. const clones = [];
  543. this._makeDomArray(target).forEach((el) => {
  544. const clonedSelf = this.clone().toArray();
  545. const { parent } = el;
  546. if (!parent) {
  547. return;
  548. }
  549. const siblings = parent.children;
  550. const index = siblings.indexOf(el);
  551. // If not found, move on
  552. /* istanbul ignore next */
  553. if (index < 0)
  554. return;
  555. // Add cloned `this` element(s) after target element
  556. uniqueSplice(siblings, index + 1, 0, clonedSelf, parent);
  557. clones.push(...clonedSelf);
  558. });
  559. return this._make(clones);
  560. }
  561. /* eslint-disable jsdoc/check-param-names*/
  562. /**
  563. * Insert content previous to each element in the set of matched elements.
  564. *
  565. * @category Manipulation
  566. * @example
  567. *
  568. * ```js
  569. * $('.apple').before('<li class="plum">Plum</li>');
  570. * $.html();
  571. * //=> <ul id="fruits">
  572. * // <li class="plum">Plum</li>
  573. * // <li class="apple">Apple</li>
  574. * // <li class="orange">Orange</li>
  575. * // <li class="pear">Pear</li>
  576. * // </ul>
  577. * ```
  578. *
  579. * @param content - HTML string, DOM element, array of DOM elements or Cheerio
  580. * to insert before each element in the set of matched elements.
  581. * @returns The instance itself.
  582. * @see {@link https://api.jquery.com/before/}
  583. */
  584. export function before(...elems) {
  585. const lastIdx = this.length - 1;
  586. return domEach(this, (el, i) => {
  587. const { parent } = el;
  588. if (!hasChildren(el) || !parent) {
  589. return;
  590. }
  591. const siblings = parent.children;
  592. const index = siblings.indexOf(el);
  593. // If not found, move on
  594. /* istanbul ignore next */
  595. if (index < 0)
  596. return;
  597. const domSrc = typeof elems[0] === 'function'
  598. ? elems[0].call(el, i, this._render(el.children))
  599. : elems;
  600. const dom = this._makeDomArray(domSrc, i < lastIdx);
  601. // Add element before `el` element
  602. uniqueSplice(siblings, index, 0, dom, parent);
  603. });
  604. }
  605. /* eslint-enable jsdoc/check-param-names*/
  606. /**
  607. * Insert every element in the set of matched elements before the target.
  608. *
  609. * @category Manipulation
  610. * @example
  611. *
  612. * ```js
  613. * $('<li class="plum">Plum</li>').insertBefore('.apple');
  614. * $.html();
  615. * //=> <ul id="fruits">
  616. * // <li class="plum">Plum</li>
  617. * // <li class="apple">Apple</li>
  618. * // <li class="orange">Orange</li>
  619. * // <li class="pear">Pear</li>
  620. * // </ul>
  621. * ```
  622. *
  623. * @param target - Element to insert elements before.
  624. * @returns The set of newly inserted elements.
  625. * @see {@link https://api.jquery.com/insertBefore/}
  626. */
  627. export function insertBefore(target) {
  628. const targetArr = this._make(target);
  629. this.remove();
  630. const clones = [];
  631. domEach(targetArr, (el) => {
  632. const clonedSelf = this.clone().toArray();
  633. const { parent } = el;
  634. if (!parent) {
  635. return;
  636. }
  637. const siblings = parent.children;
  638. const index = siblings.indexOf(el);
  639. // If not found, move on
  640. /* istanbul ignore next */
  641. if (index < 0)
  642. return;
  643. // Add cloned `this` element(s) after target element
  644. uniqueSplice(siblings, index, 0, clonedSelf, parent);
  645. clones.push(...clonedSelf);
  646. });
  647. return this._make(clones);
  648. }
  649. /**
  650. * Removes the set of matched elements from the DOM and all their children.
  651. * `selector` filters the set of matched elements to be removed.
  652. *
  653. * @category Manipulation
  654. * @example
  655. *
  656. * ```js
  657. * $('.pear').remove();
  658. * $.html();
  659. * //=> <ul id="fruits">
  660. * // <li class="apple">Apple</li>
  661. * // <li class="orange">Orange</li>
  662. * // </ul>
  663. * ```
  664. *
  665. * @param selector - Optional selector for elements to remove.
  666. * @returns The instance itself.
  667. * @see {@link https://api.jquery.com/remove/}
  668. */
  669. export function remove(selector) {
  670. // Filter if we have selector
  671. const elems = selector ? this.filter(selector) : this;
  672. domEach(elems, (el) => {
  673. removeElement(el);
  674. el.prev = el.next = el.parent = null;
  675. });
  676. return this;
  677. }
  678. /**
  679. * Replaces matched elements with `content`.
  680. *
  681. * @category Manipulation
  682. * @example
  683. *
  684. * ```js
  685. * const plum = $('<li class="plum">Plum</li>');
  686. * $('.pear').replaceWith(plum);
  687. * $.html();
  688. * //=> <ul id="fruits">
  689. * // <li class="apple">Apple</li>
  690. * // <li class="orange">Orange</li>
  691. * // <li class="plum">Plum</li>
  692. * // </ul>
  693. * ```
  694. *
  695. * @param content - Replacement for matched elements.
  696. * @returns The instance itself.
  697. * @see {@link https://api.jquery.com/replaceWith/}
  698. */
  699. export function replaceWith(content) {
  700. return domEach(this, (el, i) => {
  701. const { parent } = el;
  702. if (!parent) {
  703. return;
  704. }
  705. const siblings = parent.children;
  706. const cont = typeof content === 'function' ? content.call(el, i, el) : content;
  707. const dom = this._makeDomArray(cont);
  708. /*
  709. * In the case that `dom` contains nodes that already exist in other
  710. * structures, ensure those nodes are properly removed.
  711. */
  712. updateDOM(dom, null);
  713. const index = siblings.indexOf(el);
  714. // Completely remove old element
  715. uniqueSplice(siblings, index, 1, dom, parent);
  716. if (!dom.includes(el)) {
  717. el.parent = el.prev = el.next = null;
  718. }
  719. });
  720. }
  721. /**
  722. * Empties an element, removing all its children.
  723. *
  724. * @category Manipulation
  725. * @example
  726. *
  727. * ```js
  728. * $('ul').empty();
  729. * $.html();
  730. * //=> <ul id="fruits"></ul>
  731. * ```
  732. *
  733. * @returns The instance itself.
  734. * @see {@link https://api.jquery.com/empty/}
  735. */
  736. export function empty() {
  737. return domEach(this, (el) => {
  738. if (!hasChildren(el))
  739. return;
  740. el.children.forEach((child) => {
  741. child.next = child.prev = child.parent = null;
  742. });
  743. el.children.length = 0;
  744. });
  745. }
  746. export function html(str) {
  747. if (str === undefined) {
  748. const el = this[0];
  749. if (!el || !hasChildren(el))
  750. return null;
  751. return this._render(el.children);
  752. }
  753. return domEach(this, (el) => {
  754. if (!hasChildren(el))
  755. return;
  756. el.children.forEach((child) => {
  757. child.next = child.prev = child.parent = null;
  758. });
  759. const content = isCheerio(str)
  760. ? str.toArray()
  761. : this._parse(`${str}`, this.options, false, el).children;
  762. updateDOM(content, el);
  763. });
  764. }
  765. /**
  766. * Turns the collection to a string. Alias for `.html()`.
  767. *
  768. * @category Manipulation
  769. * @returns The rendered document.
  770. */
  771. export function toString() {
  772. return this._render(this);
  773. }
  774. export function text(str) {
  775. // If `str` is undefined, act as a "getter"
  776. if (str === undefined) {
  777. return staticText(this);
  778. }
  779. if (typeof str === 'function') {
  780. // Function support
  781. return domEach(this, (el, i) => this._make(el).text(str.call(el, i, staticText([el]))));
  782. }
  783. // Append text node to each selected elements
  784. return domEach(this, (el) => {
  785. if (!hasChildren(el))
  786. return;
  787. el.children.forEach((child) => {
  788. child.next = child.prev = child.parent = null;
  789. });
  790. const textNode = new Text(`${str}`);
  791. updateDOM(textNode, el);
  792. });
  793. }
  794. /**
  795. * Clone the cheerio object.
  796. *
  797. * @category Manipulation
  798. * @example
  799. *
  800. * ```js
  801. * const moreFruit = $('#fruits').clone();
  802. * ```
  803. *
  804. * @returns The cloned object.
  805. * @see {@link https://api.jquery.com/clone/}
  806. */
  807. export function clone() {
  808. return this._make(cloneDom(this.get()));
  809. }
  810. //# sourceMappingURL=manipulation.js.map