/** * Module dependencies */ var serialize = require('dom-serializer'), defaultOptions = require('./options').default, flattenOptions = require('./options').flatten, select = require('css-select'), parse = require('./parse'), _ = { merge: require('lodash/merge'), defaults: require('lodash/defaults') }; /** * $.load(str) */ exports.load = function(content, options, isDocument) { var Cheerio = require('./cheerio'); options = _.defaults(flattenOptions(options || {}), defaultOptions); if (isDocument === void 0) isDocument = true; var root = parse(content, options, isDocument); 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) { // 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(flattenOptions(options || {}), this._options, defaultOptions); return render(this, dom, options); }; /** * $.xml([selector | dom]) */ exports.xml = function(dom) { var options = _.defaults({xml: 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' && elem.tagName !== 'script' && elem.tagName !== 'style') { 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, defaultOptions, false); 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; }; /** * $.merge() */ exports.merge = function(arr1, arr2) { if(!(isArrayLike(arr1) && isArrayLike(arr2))){ return; } var newLength = arr1.length + arr2.length; var i = 0; while(i < arr2.length){ arr1[i + arr1.length] = arr2[i]; i++; } arr1.length = newLength; return arr1; }; function isArrayLike(item){ if(Array.isArray(item)){ return true; } if(typeof item !== 'object'){ return false; } if(!item.hasOwnProperty('length')){ return false; } if(typeof item.length !== 'number') { return false; } if(item.length < 0){ return false; } var i = 0; while(i < item.length){ if(!(i in item)){ return false; } i++; } return true; }