| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425 |
- var parse = require('../parse'),
- $ = require('../static'),
- updateDOM = parse.update,
- evaluate = parse.evaluate,
- utils = require('../utils'),
- domEach = utils.domEach,
- cloneDom = utils.cloneDom,
- isHtml = utils.isHtml,
- slice = Array.prototype.slice,
- _ = {
- flatten: require('lodash.flatten'),
- bind: require('lodash.bind'),
- forEach: require('lodash.foreach')
- };
- // Create an array of nodes, recursing into arrays and parsing strings if
- // necessary
- exports._makeDomArray = function makeDomArray(elem, clone) {
- if (elem == null) {
- return [];
- } else if (elem.cheerio) {
- return clone ? cloneDom(elem.get(), elem.options) : elem.get();
- } else if (Array.isArray(elem)) {
- return _.flatten(elem.map(function(el) {
- return this._makeDomArray(el, clone);
- }, this));
- } else if (typeof elem === 'string') {
- return evaluate(elem, this.options);
- } else {
- return clone ? cloneDom([elem]) : [elem];
- }
- };
- var _insert = function(concatenator) {
- return function() {
- var elems = slice.call(arguments),
- lastIdx = this.length - 1;
- return domEach(this, function(i, el) {
- var dom, domSrc;
- if (typeof elems[0] === 'function') {
- domSrc = elems[0].call(el, i, $.html(el.children));
- } else {
- domSrc = elems;
- }
- 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.
- *
- * @param {Array} array Target array to splice.
- * @param {Number} spliceIdx Index at which to begin changing the array.
- * @param {Number} spliceCount Number of elements to remove from the array.
- * @param {Array} newElems Elements to insert into the array.
- *
- * @api private
- */
- var uniqueSplice = function(array, spliceIdx, spliceCount, newElems, parent) {
- var spliceArgs = [spliceIdx, spliceCount].concat(newElems),
- prev = array[spliceIdx - 1] || null,
- next = array[spliceIdx] || null;
- var idx, len, prevIdx, node, oldParent;
- // Before splicing in new elements, ensure they do not already appear in the
- // current array.
- for (idx = 0, len = newElems.length; idx < len; ++idx) {
- node = newElems[idx];
- oldParent = node.parent || node.root;
- prevIdx = oldParent && oldParent.children.indexOf(newElems[idx]);
- if (oldParent && prevIdx > -1) {
- oldParent.children.splice(prevIdx, 1);
- if (parent === oldParent && spliceIdx > prevIdx) {
- spliceArgs[0]--;
- }
- }
- node.root = null;
- node.parent = parent;
- if (node.prev) {
- node.prev.next = node.next || null;
- }
- if (node.next) {
- node.next.prev = node.prev || null;
- }
- node.prev = newElems[idx - 1] || prev;
- node.next = newElems[idx + 1] || next;
- }
- if (prev) {
- prev.next = newElems[0];
- }
- if (next) {
- next.prev = newElems[newElems.length - 1];
- }
- return array.splice.apply(array, spliceArgs);
- };
- exports.appendTo = function(target) {
- if (!target.cheerio) {
- target = this.constructor.call(this.constructor, target, null, this._originalRoot);
- }
- target.append(this);
- return this;
- };
- exports.prependTo = function(target) {
- if (!target.cheerio) {
- target = this.constructor.call(this.constructor, target, null, this._originalRoot);
- }
- target.prepend(this);
- return this;
- };
- exports.append = _insert(function(dom, children, parent) {
- uniqueSplice(children, children.length, 0, dom, parent);
- });
- exports.prepend = _insert(function(dom, children, parent) {
- uniqueSplice(children, 0, 0, dom, parent);
- });
- exports.wrap = function(wrapper) {
- var wrapperFn = typeof wrapper === 'function' && wrapper,
- lastIdx = this.length - 1;
- _.forEach(this, _.bind(function(el, i) {
- var parent = el.parent || el.root,
- siblings = parent.children,
- dom, index;
- if (!parent) {
- return;
- }
- if (wrapperFn) {
- wrapper = wrapperFn.call(el, i);
- }
- if (typeof wrapper === 'string' && !isHtml(wrapper)) {
- wrapper = this.parents().last().find(wrapper).clone();
- }
- dom = this._makeDomArray(wrapper, i < lastIdx).slice(0, 1);
- index = siblings.indexOf(el);
- updateDOM([el], dom[0]);
- // 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, dom, parent);
- }, this));
- return this;
- };
- exports.after = function() {
- var elems = slice.call(arguments),
- lastIdx = this.length - 1;
- domEach(this, function(i, el) {
- var parent = el.parent || el.root;
- if (!parent) {
- return;
- }
- var siblings = parent.children,
- index = siblings.indexOf(el),
- domSrc, dom;
- // If not found, move on
- if (index < 0) return;
- if (typeof elems[0] === 'function') {
- domSrc = elems[0].call(el, i, $.html(el.children));
- } else {
- domSrc = elems;
- }
- dom = this._makeDomArray(domSrc, i < lastIdx);
- // Add element after `this` element
- uniqueSplice(siblings, index + 1, 0, dom, parent);
- });
- return this;
- };
- exports.insertAfter = function(target) {
- var clones = [],
- self = this;
- if (typeof target === 'string') {
- target = this.constructor.call(this.constructor, target, null, this._originalRoot);
- }
- target = this._makeDomArray(target);
- self.remove();
- domEach(target, function(i, el) {
- var clonedSelf = self._makeDomArray(self.clone());
- var parent = el.parent || el.root;
- if (!parent) {
- return;
- }
- var siblings = parent.children,
- index = siblings.indexOf(el);
- // If not found, move on
- if (index < 0) return;
- // Add cloned `this` element(s) after target element
- uniqueSplice(siblings, index + 1, 0, clonedSelf, parent);
- clones.push(clonedSelf);
- });
- return this.constructor.call(this.constructor, this._makeDomArray(clones));
- };
- exports.before = function() {
- var elems = slice.call(arguments),
- lastIdx = this.length - 1;
- domEach(this, function(i, el) {
- var parent = el.parent || el.root;
- if (!parent) {
- return;
- }
- var siblings = parent.children,
- index = siblings.indexOf(el),
- domSrc, dom;
- // If not found, move on
- if (index < 0) return;
- if (typeof elems[0] === 'function') {
- domSrc = elems[0].call(el, i, $.html(el.children));
- } else {
- domSrc = elems;
- }
- dom = this._makeDomArray(domSrc, i < lastIdx);
- // Add element before `el` element
- uniqueSplice(siblings, index, 0, dom, parent);
- });
- return this;
- };
- exports.insertBefore = function(target) {
- var clones = [],
- self = this;
- if (typeof target === 'string') {
- target = this.constructor.call(this.constructor, target, null, this._originalRoot);
- }
- target = this._makeDomArray(target);
- self.remove();
- domEach(target, function(i, el) {
- var clonedSelf = self._makeDomArray(self.clone());
- var parent = el.parent || el.root;
- if (!parent) {
- return;
- }
- var siblings = parent.children,
- index = siblings.indexOf(el);
- // If not found, move on
- if (index < 0) return;
- // Add cloned `this` element(s) after target element
- uniqueSplice(siblings, index, 0, clonedSelf, parent);
- clones.push(clonedSelf);
- });
- return this.constructor.call(this.constructor, this._makeDomArray(clones));
- };
- /*
- remove([selector])
- */
- exports.remove = function(selector) {
- var elems = this;
- // Filter if we have selector
- if (selector)
- elems = elems.filter(selector);
- domEach(elems, function(i, el) {
- var parent = el.parent || el.root;
- if (!parent) {
- return;
- }
- var siblings = parent.children,
- index = siblings.indexOf(el);
- if (index < 0) return;
- siblings.splice(index, 1);
- if (el.prev) {
- el.prev.next = el.next;
- }
- if (el.next) {
- el.next.prev = el.prev;
- }
- el.prev = el.next = el.parent = el.root = null;
- });
- return this;
- };
- exports.replaceWith = function(content) {
- var self = this;
- domEach(this, function(i, el) {
- var parent = el.parent || el.root;
- if (!parent) {
- return;
- }
- var siblings = parent.children,
- dom = self._makeDomArray(typeof content === 'function' ? content.call(el, i, el) : content),
- index;
- // In the case that `dom` contains nodes that already exist in other
- // structures, ensure those nodes are properly removed.
- updateDOM(dom, null);
- index = siblings.indexOf(el);
- // Completely remove old element
- uniqueSplice(siblings, index, 1, dom, parent);
- el.parent = el.prev = el.next = el.root = null;
- });
- return this;
- };
- exports.empty = function() {
- domEach(this, function(i, el) {
- _.forEach(el.children, function(el) {
- el.next = el.prev = el.parent = null;
- });
- el.children.length = 0;
- });
- return this;
- };
- /**
- * Set/Get the HTML
- */
- exports.html = function(str) {
- if (str === undefined) {
- if (!this[0] || !this[0].children) return null;
- return $.html(this[0].children, this.options);
- }
- var opts = this.options;
- domEach(this, function(i, el) {
- _.forEach(el.children, function(el) {
- el.next = el.prev = el.parent = null;
- });
- var content = str.cheerio ? str.clone().get() : evaluate('' + str, opts);
- updateDOM(content, el);
- });
- return this;
- };
- exports.toString = function() {
- return $.html(this, this.options);
- };
- exports.text = function(str) {
- // If `str` is undefined, act as a "getter"
- if (str === undefined) {
- return $.text(this);
- } else if (typeof str === 'function') {
- // Function support
- return domEach(this, function(i, el) {
- var $el = [el];
- return exports.text.call($el, str.call(el, i, $.text($el)));
- });
- }
- // Append text node to each selected elements
- domEach(this, function(i, el) {
- _.forEach(el.children, function(el) {
- el.next = el.prev = el.parent = null;
- });
- var elem = {
- data: '' + str,
- type: 'text',
- parent: el,
- prev: null,
- next: null,
- children: []
- };
- updateDOM(elem, el);
- });
- return this;
- };
- exports.clone = function() {
- return this._make(cloneDom(this.get(), this.options));
- };
|