manipulation.js 24 KB

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