text: var _ = require('lodash'),
      select = require('CSSselect'),
      utils = require('../utils'),
      domEach = utils.domEach,
      isTag = utils.isTag;
  
  var find = exports.find = function(selector) {
    var elems = _.reduce(this, function(memo, elem) {
      return memo.concat(_.filter(elem.children, isTag));
    }, []);
  
    return this._make(select(selector, elems));
  };
  
  // Get the parent of each element in the current set of matched elements,
  // optionally filtered by a selector.
  var parent = exports.parent = function(selector) {
    var set = [];
    var $set;
  
    domEach(this, function(idx, elem) {
      var parentElem = elem.parent;
      if (parentElem && set.indexOf(parentElem) < 0) {
        set.push(parentElem);
      }
    });
  
    if (arguments.length) {
      set = filter.call(set, selector, this);
    }
  
    return this._make(set);
  };
  
  var 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);
  };
  
  // 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.
  var closest = 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);
  };
  
  var next = 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 ? filter.call(elems, selector, this) : this._make(elems);
  };
  
  var nextAll = 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 ? filter.call(elems, selector, this) : this._make(elems);
  };
  
  var nextUntil = exports.nextUntil = function(selector, filterSelector) {
    if (!this[0]) { return this; }
    var elems = [], untilNode, untilNodes;
  
    if (typeof selector === 'string') {
      untilNode = select(selector, this.nextAll().get())[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 ?
      filter.call(elems, filterSelector, this) :
      this._make(elems);
  };
  
  var prev = 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 ? filter.call(elems, selector, this) : this._make(elems);
  };
  
  var prevAll = 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 ? filter.call(elems, selector, this) : this._make(elems);
  };
  
  var prevUntil = exports.prevUntil = function(selector, filterSelector) {
    if (!this[0]) { return this; }
    var elems = [], untilNode, untilNodes;
  
    if (typeof selector === 'string') {
      untilNode = select(selector, this.prevAll().get())[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 ?
      filter.call(elems, filterSelector, this) :
      this._make(elems);
  };
  
  var siblings = 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 filter.call(elems, selector, this);
    } else {
      return this._make(elems);
    }
  };
  
  var children = 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);
    else if (_.isNumber(selector)) return this._make(elems[selector]);
  
    return filter.call(elems, selector, this);
  };
  
  var contents = exports.contents = function() {
    return this._make(_.reduce(this, function(all, elem) {
      all.push.apply(all, elem.children);
      return all;
    }, []));
  };
  
  var each = exports.each = function(fn) {
    var i = 0, len = this.length;
    while (i < len && fn.call(this._make(this[i]), i, this[i]) !== false) ++i;
    return this;
  };
  
  var map = 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 filter = exports.filter = function(match, container) {
    container = container || this;
  
    var make = _.bind(container._make, container);
    var filterFn;
  
    if (_.isString(match)) {
      filterFn = select.compile(match);
    } else if (_.isFunction(match)) {
      filterFn = function(el, i) {
        return match.call(make(el), i, el);
      };
    } else if (match.cheerio) {
      filterFn = match.is.bind(match);
    } else {
      filterFn = function(el) {
        return match === el;
      };
    }
  
    return make(_.filter(this, filterFn));
  };
  
  var first = exports.first = function() {
    return this.length > 1 ? this._make(this[0]) : this;
  };
  
  var last = 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.
  var eq = 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.
  var get = exports.get = function(i) {
    if (i == null) {
      return Array.prototype.slice.call(this);
    } else {
      return this[i < 0 ? (this.length + i) : i];
    }
  };
  
  var slice = 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 || 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.
  var end = exports.end = function() {
    return this.prevObject || this._make([]);
  };
  