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