manipulation.js 24 KB

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