| 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)); |
| }; |