Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wrapper function for functions with similar functionality #1662

Closed
wants to merge 11 commits into from
Closed
242 changes: 108 additions & 134 deletions lib/api/traversing.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,62 @@ var uniqueSort = require('htmlparser2').DomUtils.uniqueSort;
var isTag = utils.isTag;
var reSiblingSelector = /^\s*[~+]/;

/**
* Matcher provides function, what finds elements based on function provided.
* It also houses filtering. What may provided later when function is called.
*
* @private
* @param {string} name - Function name.
* @param {Function} fn - Function for collecting elements.
* @returns {Function} - Wrapped function.
*/
function _matcher(name, fn) {
// customized Map, discards null elements
function matchMap(elems) {
var len = elems.length;
var value;
var i = 0;
var ret = [];
for (; i < len; i++) {
value = fn(elems[i], i);
if (value !== null) {
ret.push(value);
}
}
return Array.prototype.concat.apply([], ret);
fb55 marked this conversation as resolved.
Show resolved Hide resolved
}

return function (selector) {
if (this[0]) {
var matched = matchMap(this);

// select.filter uses uniqueSort already internally
if (selector) {
if (typeof selector === 'string') {
matched = select.filter(selector, matched, this.options);
} else {
matched = matched.filter(getFilterFn(selector));
}
}

// Sorting happend only if collection had more than one elements
if (this.length > 1) {
if (!(name === 'next' || name === 'prev' || name === 'children')) {
matched = uniqueSort(matched);
}

// Reverse order
if (name === 'parents' || name === 'prevAll') {
matched.reverse();
}
}

return this._make(matched);
}
return this;
};
}

/**
* Get the descendants of each element in the current set of matched elements,
* filtered by a selector, jQuery object, or element.
Expand Down Expand Up @@ -70,31 +126,16 @@ exports.find = function (selectorOrHaystack) {
* $('.pear').parent().attr('id');
* //=> fruits
*
* @function
* @param {string} [selector] - If specified filter for parent.
* @see {@link https://api.jquery.com/parent/}
*
* @returns {Cheerio} The parents.
*/
exports.parent = function (selector) {
var set = [];

domEach(this, function (_, elem) {
var parentElem = elem.parent;
if (
parentElem &&
parentElem.type !== 'root' &&
set.indexOf(parentElem) < 0
) {
set.push(parentElem);
}
});

if (selector) {
set = exports.filter.call(set, selector, this);
}

return this._make(set);
};
exports.parent = _matcher('parent', function (elem) {
var parent = elem.parent;
return parent && parent.type !== 'root' ? parent : null;
});

/**
* Get a set of parents filtered by `selector` of each element in the current
Expand All @@ -106,31 +147,19 @@ exports.parent = function (selector) {
* $('.orange').parents('#fruits').length;
* // => 1
*
* @function
* @param {string} [selector] - If specified filter for parents.
* @see {@link https://api.jquery.com/parents/}
*
* @returns {Cheerio} The parents.
*/
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.parents = _matcher('parents', function (elem) {
var matched = [];
while ((elem = elem.parent) && elem.type !== 'root') {
matched.push(elem);
}
return matched;
});

/**
* Get the ancestors of each element in the current set of matched elements, up
Expand Down Expand Up @@ -232,30 +261,16 @@ exports.closest = function (selector) {
* $('.apple').next().hasClass('orange');
* //=> true
*
* @function
* @param {string} [selector] - If specified filter for sibling.
* @see {@link https://api.jquery.com/next/}
*
* @returns {Cheerio} The next nodes.
*/
exports.next = function (selector) {
if (!this[0]) {
return this;
}
var elems = [];

domEach(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.next = _matcher('next', function (elem) {
while ((elem = elem.next) && !isTag(elem));
return elem;
});

/**
* Gets all the following siblings of the first selected element, optionally
Expand All @@ -267,29 +282,19 @@ exports.next = function (selector) {
* $('.apple').nextAll('.orange');
* //=> [<li class="orange">Orange</li>]
*
* @function
* @param {string} [selector] - If specified filter for siblings.
* @see {@link https://api.jquery.com/nextAll/}
*
* @returns {Cheerio} The next nodes.
*/
exports.nextAll = function (selector) {
if (!this[0]) {
return this;
exports.nextAll = _matcher('nextAll', function (elem) {
var matched = [];
while ((elem = elem.next)) {
if (isTag(elem)) matched.push(elem);
}
var elems = [];

domEach(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);
};
return matched;
});

/**
* Gets all the following siblings up to but not including the element matched
Expand Down Expand Up @@ -350,30 +355,17 @@ exports.nextUntil = function (selector, filterSelector) {
* $('.orange').prev().hasClass('apple');
* //=> true
*
* @function
* @param {string} [selector] - If specified filter for siblings.
* @see {@link https://api.jquery.com/prev/}
*
* @returns {Cheerio} The previous nodes.
*/
exports.prev = function (selector) {
if (!this[0]) {
return this;
}
var elems = [];

domEach(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.prev = _matcher('prev', function (elem) {
// eslint-disable-next-line no-empty
while ((elem = elem.prev) && !isTag(elem)) {}
return elem;
});

/**
* Gets all the preceding siblings of the first selected element, optionally
Expand All @@ -385,29 +377,19 @@ exports.prev = function (selector) {
* $('.pear').prevAll('.orange');
* //=> [<li class="orange">Orange</li>]
*
* @function
* @param {string} [selector] - If specified filter for siblings.
* @see {@link https://api.jquery.com/prevAll/}
*
* @returns {Cheerio} The previous nodes.
*/
exports.prevAll = function (selector) {
if (!this[0]) {
return this;
exports.prevAll = _matcher('prevAll', function (elem) {
var matched = [];
while ((elem = elem.prev)) {
if (isTag(elem)) matched.push(elem);
}
var elems = [];

domEach(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);
};
return matched;
});

/**
* Gets all the preceding siblings up to but not including the element matched
Expand Down Expand Up @@ -470,25 +452,22 @@ exports.prevUntil = function (selector, filterSelector) {
* $('.pear').siblings('.orange').length;
* //=> 1
*
* @function
* @param {string} [selector] - If specified filter for siblings.
* @see {@link https://api.jquery.com/siblings/}
*
* @returns {Cheerio} The siblings.
*/
exports.siblings = function (selector) {
var parent = this.parent();

var elems = (parent ? parent.children() : this.siblingsAndMe())
.toArray()
.filter(function (elem) {
return isTag(elem) && !this.is(elem);
}, this);

if (selector !== undefined) {
return exports.filter.call(elems, selector, this);
exports.siblings = _matcher('siblings', function (elem) {
var node = elem.parent && elem.parent.firstChild;
var matched = [];
for (; node; node = node.next) {
5saviahv marked this conversation as resolved.
Show resolved Hide resolved
if (isTag(node) && node !== elem) {
matched.push(node);
}
}
return this._make(elems);
};
return matched;
});

/**
* Gets the children of the first selected element.
Expand All @@ -500,20 +479,15 @@ exports.siblings = function (selector) {
* $('#fruits').children('.pear').text();
* //=> Pear
*
* @function
* @param {string} [selector] - If specified filter for children.
* @see {@link https://api.jquery.com/children/}
*
* @returns {Cheerio} The children.
*/
exports.children = function (selector) {
var elems = this.toArray().reduce(function (newElems, elem) {
return newElems.concat(elem.children.filter(isTag));
}, []);

if (selector === undefined) return this._make(elems);

return exports.filter.call(elems, selector, this);
};
exports.children = _matcher('children', function (elem) {
return elem.children.filter(isTag);
});

/**
* Gets the children of each element in the set of matched elements, including
Expand Down
18 changes: 18 additions & 0 deletions test/__fixtures__/fixtures.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,24 @@ exports.drinks = [
'</ul>',
].join('');

exports.eleven = [
'<html>\n<body>\n<ul>',
'<li>One</li>',
'<li>Two</li>',
'<li class="blue sel">Three</li>',
'<li class="red">Four</li>',
'</ul>\n\n<ul>',
'<li class="red">Five</li>',
'<li>Six</li>',
'<li class="blue">Seven</li>',
'</ul>\n\n<ul>',
'<li>Eight</li>',
'<li class="red sel">Nine</li>',
'<li>Ten</li>',
'<li class="sel">Eleven</li>',
'</ul>\n</body>\n</html>',
].join('\n');

exports.food = [
'<ul id="food">',
exports.fruits,
Expand Down
Loading