423 lines
11 KiB
JavaScript
423 lines
11 KiB
JavaScript
|
var _ = require('lodash'),
|
||
|
select = require('css-select'),
|
||
|
utils = require('../utils'),
|
||
|
domEach = utils.domEach,
|
||
|
uniqueSort = require('htmlparser2').DomUtils.uniqueSort,
|
||
|
isTag = utils.isTag;
|
||
|
|
||
|
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));
|
||
|
}
|
||
|
|
||
|
return this._make(select(selectorOrHaystack, elems, this.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(),
|
||
|
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
|
||
|
);
|
||
|
};
|