| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429 |
- var select = require('css-select'),
- utils = require('../utils'),
- domEach = utils.domEach,
- uniqueSort = require('htmlparser2').DomUtils.uniqueSort,
- isTag = utils.isTag,
- _ = {
- bind: require('lodash.bind'),
- forEach: require('lodash.foreach'),
- reject: require('lodash.reject'),
- filter: require('lodash.filter'),
- reduce: require('lodash.reduce')
- };
- exports.find = function(selectorOrHaystack) {
- var elems = _.reduce(this, function(memo, elem) {
- return memo.concat(_.filter(elem.children, isTag));
- }, []);
- var contains = this.constructor.contains;
- var haystack;
- if (selectorOrHaystack && typeof selectorOrHaystack !== 'string') {
- if (selectorOrHaystack.cheerio) {
- haystack = selectorOrHaystack.get();
- } else {
- haystack = [selectorOrHaystack];
- }
- return this._make(haystack.filter(function(elem) {
- var idx, len;
- for (idx = 0, len = this.length; idx < len; ++idx) {
- if (contains(this[idx], elem)) {
- return true;
- }
- }
- }, this));
- }
- var options = {__proto__: this.options, context: this.toArray()};
- return this._make(select(selectorOrHaystack, elems, options));
- };
- // Get the parent of each element in the current set of matched elements,
- // optionally filtered by a selector.
- exports.parent = function(selector) {
- var set = [];
- domEach(this, function(idx, elem) {
- var parentElem = elem.parent;
- if (parentElem && set.indexOf(parentElem) < 0) {
- set.push(parentElem);
- }
- });
- if (arguments.length) {
- set = exports.filter.call(set, selector, this);
- }
- return this._make(set);
- };
- exports.parents = function(selector) {
- var parentNodes = [];
- // When multiple DOM elements are in the original set, the resulting set will
- // be in *reverse* order of the original elements as well, with duplicates
- // removed.
- this.get().reverse().forEach(function(elem) {
- traverseParents(this, elem.parent, selector, Infinity)
- .forEach(function(node) {
- if (parentNodes.indexOf(node) === -1) {
- parentNodes.push(node);
- }
- }
- );
- }, this);
- return this._make(parentNodes);
- };
- exports.parentsUntil = function(selector, filter) {
- var parentNodes = [], untilNode, untilNodes;
- if (typeof selector === 'string') {
- untilNode = select(selector, this.parents().toArray(), this.options)[0];
- } else if (selector && selector.cheerio) {
- untilNodes = selector.toArray();
- } else if (selector) {
- untilNode = selector;
- }
- // When multiple DOM elements are in the original set, the resulting set will
- // be in *reverse* order of the original elements as well, with duplicates
- // removed.
- this.toArray().reverse().forEach(function(elem) {
- while ((elem = elem.parent)) {
- if ((untilNode && elem !== untilNode) ||
- (untilNodes && untilNodes.indexOf(elem) === -1) ||
- (!untilNode && !untilNodes)) {
- if (isTag(elem) && parentNodes.indexOf(elem) === -1) { parentNodes.push(elem); }
- } else {
- break;
- }
- }
- }, this);
- return this._make(filter ? select(filter, parentNodes, this.options) : parentNodes);
- };
- // For each element in the set, get the first element that matches the selector
- // by testing the element itself and traversing up through its ancestors in the
- // DOM tree.
- exports.closest = function(selector) {
- var set = [];
- if (!selector) {
- return this._make(set);
- }
- domEach(this, function(idx, elem) {
- var closestElem = traverseParents(this, elem, selector, 1)[0];
- // Do not add duplicate elements to the set
- if (closestElem && set.indexOf(closestElem) < 0) {
- set.push(closestElem);
- }
- }.bind(this));
- return this._make(set);
- };
- exports.next = function(selector) {
- if (!this[0]) { return this; }
- var elems = [];
- _.forEach(this, function(elem) {
- while ((elem = elem.next)) {
- if (isTag(elem)) {
- elems.push(elem);
- return;
- }
- }
- });
- return selector ?
- exports.filter.call(elems, selector, this) :
- this._make(elems);
- };
- exports.nextAll = function(selector) {
- if (!this[0]) { return this; }
- var elems = [];
- _.forEach(this, function(elem) {
- while ((elem = elem.next)) {
- if (isTag(elem) && elems.indexOf(elem) === -1) {
- elems.push(elem);
- }
- }
- });
- return selector ?
- exports.filter.call(elems, selector, this) :
- this._make(elems);
- };
- exports.nextUntil = function(selector, filterSelector) {
- if (!this[0]) { return this; }
- var elems = [], untilNode, untilNodes;
- if (typeof selector === 'string') {
- untilNode = select(selector, this.nextAll().get(), this.options)[0];
- } else if (selector && selector.cheerio) {
- untilNodes = selector.get();
- } else if (selector) {
- untilNode = selector;
- }
- _.forEach(this, function(elem) {
- while ((elem = elem.next)) {
- if ((untilNode && elem !== untilNode) ||
- (untilNodes && untilNodes.indexOf(elem) === -1) ||
- (!untilNode && !untilNodes)) {
- if (isTag(elem) && elems.indexOf(elem) === -1) {
- elems.push(elem);
- }
- } else {
- break;
- }
- }
- });
- return filterSelector ?
- exports.filter.call(elems, filterSelector, this) :
- this._make(elems);
- };
- exports.prev = function(selector) {
- if (!this[0]) { return this; }
- var elems = [];
- _.forEach(this, function(elem) {
- while ((elem = elem.prev)) {
- if (isTag(elem)) {
- elems.push(elem);
- return;
- }
- }
- });
- return selector ?
- exports.filter.call(elems, selector, this) :
- this._make(elems);
- };
- exports.prevAll = function(selector) {
- if (!this[0]) { return this; }
- var elems = [];
- _.forEach(this, function(elem) {
- while ((elem = elem.prev)) {
- if (isTag(elem) && elems.indexOf(elem) === -1) {
- elems.push(elem);
- }
- }
- });
- return selector ?
- exports.filter.call(elems, selector, this) :
- this._make(elems);
- };
- exports.prevUntil = function(selector, filterSelector) {
- if (!this[0]) { return this; }
- var elems = [], untilNode, untilNodes;
- if (typeof selector === 'string') {
- untilNode = select(selector, this.prevAll().get(), this.options)[0];
- } else if (selector && selector.cheerio) {
- untilNodes = selector.get();
- } else if (selector) {
- untilNode = selector;
- }
- _.forEach(this, function(elem) {
- while ((elem = elem.prev)) {
- if ((untilNode && elem !== untilNode) ||
- (untilNodes && untilNodes.indexOf(elem) === -1) ||
- (!untilNode && !untilNodes)) {
- if (isTag(elem) && elems.indexOf(elem) === -1) {
- elems.push(elem);
- }
- } else {
- break;
- }
- }
- });
- return filterSelector ?
- exports.filter.call(elems, filterSelector, this) :
- this._make(elems);
- };
- exports.siblings = function(selector) {
- var parent = this.parent();
- var elems = _.filter(
- parent ? parent.children() : this.siblingsAndMe(),
- _.bind(function(elem) { return isTag(elem) && !this.is(elem); }, this)
- );
- if (selector !== undefined) {
- return exports.filter.call(elems, selector, this);
- } else {
- return this._make(elems);
- }
- };
- exports.children = function(selector) {
- var elems = _.reduce(this, function(memo, elem) {
- return memo.concat(_.filter(elem.children, isTag));
- }, []);
- if (selector === undefined) return this._make(elems);
- return exports.filter.call(elems, selector, this);
- };
- exports.contents = function() {
- return this._make(_.reduce(this, function(all, elem) {
- all.push.apply(all, elem.children);
- return all;
- }, []));
- };
- exports.each = function(fn) {
- var i = 0, len = this.length;
- while (i < len && fn.call(this[i], i, this[i]) !== false) ++i;
- return this;
- };
- exports.map = function(fn) {
- return this._make(_.reduce(this, function(memo, el, i) {
- var val = fn.call(el, i, el);
- return val == null ? memo : memo.concat(val);
- }, []));
- };
- var makeFilterMethod = function(filterFn) {
- return function(match, container) {
- var testFn;
- container = container || this;
- if (typeof match === 'string') {
- testFn = select.compile(match, container.options);
- } else if (typeof match === 'function') {
- testFn = function(el, i) {
- return match.call(el, i, el);
- };
- } else if (match.cheerio) {
- testFn = match.is.bind(match);
- } else {
- testFn = function(el) {
- return match === el;
- };
- }
- return container._make(filterFn(this, testFn));
- };
- };
- exports.filter = makeFilterMethod(_.filter);
- exports.not = makeFilterMethod(_.reject);
- exports.has = function(selectorOrHaystack) {
- var that = this;
- return exports.filter.call(this, function() {
- return that._make(this).find(selectorOrHaystack).length > 0;
- });
- };
- exports.first = function() {
- return this.length > 1 ? this._make(this[0]) : this;
- };
- exports.last = function() {
- return this.length > 1 ? this._make(this[this.length - 1]) : this;
- };
- // Reduce the set of matched elements to the one at the specified index.
- exports.eq = function(i) {
- i = +i;
- // Use the first identity optimization if possible
- if (i === 0 && this.length <= 1) return this;
- if (i < 0) i = this.length + i;
- return this[i] ? this._make(this[i]) : this._make([]);
- };
- // Retrieve the DOM elements matched by the jQuery object.
- exports.get = function(i) {
- if (i == null) {
- return Array.prototype.slice.call(this);
- } else {
- return this[i < 0 ? (this.length + i) : i];
- }
- };
- // Search for a given element from among the matched elements.
- exports.index = function(selectorOrNeedle) {
- var $haystack, needle;
- if (arguments.length === 0) {
- $haystack = this.parent().children();
- needle = this[0];
- } else if (typeof selectorOrNeedle === 'string') {
- $haystack = this._make(selectorOrNeedle);
- needle = this[0];
- } else {
- $haystack = this;
- needle = selectorOrNeedle.cheerio ? selectorOrNeedle[0] : selectorOrNeedle;
- }
- return $haystack.get().indexOf(needle);
- };
- exports.slice = function() {
- return this._make([].slice.apply(this, arguments));
- };
- function traverseParents(self, elem, selector, limit) {
- var elems = [];
- while (elem && elems.length < limit) {
- if (!selector || exports.filter.call([elem], selector, self).length) {
- elems.push(elem);
- }
- elem = elem.parent;
- }
- return elems;
- }
- // End the most recent filtering operation in the current chain and return the
- // set of matched elements to its previous state.
- exports.end = function() {
- return this.prevObject || this._make([]);
- };
- exports.add = function(other, context) {
- var selection = this._make(other, context);
- var contents = uniqueSort(selection.get().concat(this.get()));
- for (var i = 0; i < contents.length; ++i) {
- selection[i] = contents[i];
- }
- selection.length = contents.length;
- return selection;
- };
- // Add the previous set of elements on the stack to the current set, optionally
- // filtered by a selector.
- exports.addBack = function(selector) {
- return this.add(
- arguments.length ? this.prevObject.filter(selector) : this.prevObject
- );
- };
|