/** * Module dependencies */ var serialize = require('dom-serializer'), select = require('css-select'), parse = require('./parse'), _ = { merge: require('lodash.merge'), defaults: require('lodash.defaults') }; /** * $.load(str) */ exports.load = function(content, options) { var Cheerio = require('./cheerio'); options = _.defaults(options || {}, Cheerio.prototype.options); var root = parse(content, options); var initialize = function(selector, context, r, opts) { if (!(this instanceof initialize)) { return new initialize(selector, context, r, opts); } opts = _.defaults(opts || {}, options); return Cheerio.call(this, selector, context, r || root, opts); }; // Ensure that selections created by the "loaded" `initialize` function are // true Cheerio instances. initialize.prototype = Object.create(Cheerio.prototype); initialize.prototype.constructor = initialize; // Mimic jQuery's prototype alias for plugin authors. initialize.fn = initialize.prototype; // Keep a reference to the top-level scope so we can chain methods that implicitly // resolve selectors; e.g. $("").(".bar"), which otherwise loses ._root initialize.prototype._originalRoot = root; // Add in the static methods _.merge(initialize, exports); // Add in the root initialize._root = root; // store options initialize._options = options; return initialize; }; /* * Helper function */ function render(that, dom, options) { if (!dom) { if (that._root && that._root.children) { dom = that._root.children; } else { return ''; } } else if (typeof dom === 'string') { dom = select(dom, that._root, options); } return serialize(dom, options); } /** * $.html([selector | dom], [options]) */ exports.html = function(dom, options) { var Cheerio = require('./cheerio'); // be flexible about parameters, sometimes we call html(), // with options as only parameter // check dom argument for dom element specific properties // assume there is no 'length' or 'type' properties in the options object if (Object.prototype.toString.call(dom) === '[object Object]' && !options && !('length' in dom) && !('type' in dom)) { options = dom; dom = undefined; } // sometimes $.html() used without preloading html // so fallback non existing options to the default ones options = _.defaults(options || {}, this._options, Cheerio.prototype.options); return render(this, dom, options); }; /** * $.xml([selector | dom]) */ exports.xml = function(dom) { var options = _.defaults({xmlMode: true}, this._options); return render(this, dom, options); }; /** * $.text(dom) */ exports.text = function(elems) { if (!elems) { elems = this.root(); } var ret = '', len = elems.length, elem; for (var i = 0; i < len; i++) { elem = elems[i]; if (elem.type === 'text') ret += elem.data; else if (elem.children && elem.type !== 'comment') { ret += exports.text(elem.children); } } return ret; }; /** * $.parseHTML(data [, context ] [, keepScripts ]) * Parses a string into an array of DOM nodes. The `context` argument has no * meaning for Cheerio, but it is maintained for API compatibility with jQuery. */ exports.parseHTML = function(data, context, keepScripts) { var parsed; if (!data || typeof data !== 'string') { return null; } if (typeof context === 'boolean') { keepScripts = context; } parsed = this.load(data); if (!keepScripts) { parsed('script').remove(); } // The `children` array is used by Cheerio internally to group elements that // share the same parents. When nodes created through `parseHTML` are // inserted into previously-existing DOM structures, they will be removed // from the `children` array. The results of `parseHTML` should remain // constant across these operations, so a shallow copy should be returned. return parsed.root()[0].children.slice(); }; /** * $.root() */ exports.root = function() { return this(this._root); }; /** * $.contains() */ exports.contains = function(container, contained) { // According to the jQuery API, an element does not "contain" itself if (contained === container) { return false; } // Step up the descendants, stopping when the root element is reached // (signaled by `.parent` returning a reference to the same object) while (contained && contained !== contained.parent) { contained = contained.parent; if (contained === container) { return true; } } return false; };