1
restaurant-search/scrape/node_modules/cheerio/lib/api/attributes.js

414 lines
11 KiB
JavaScript
Raw Normal View History

2014-07-08 04:35:52 +00:00
var _ = require('lodash'),
utils = require('../utils'),
isTag = utils.isTag,
domEach = utils.domEach,
hasOwn = Object.prototype.hasOwnProperty,
camelCase = utils.camelCase,
cssCase = utils.cssCase,
rspace = /\s+/,
dataAttrPrefix = 'data-',
// Lookup table for coercing string data-* attributes to their corresponding
// JavaScript primitives
primitives = {
null: null,
true: true,
false: false
},
// Attributes that are booleans
rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,
// Matches strings that look like JSON objects or arrays
rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/;
var getAttr = function(elem, name) {
if (!elem || !isTag(elem)) return;
if (!elem.attribs) {
elem.attribs = {};
}
// Return the entire attribs object if no attribute specified
if (!name) {
return elem.attribs;
}
if (hasOwn.call(elem.attribs, name)) {
// Get the (decoded) attribute
return elem.attribs[name];
}
};
var setAttr = function(el, name, value) {
if (value === null) {
removeAttribute(el, name);
} else {
el.attribs[name] = value+'';
}
};
var attr = exports.attr = function(name, value) {
// Set the value (with attr map support)
if (typeof name === 'object' || value !== undefined) {
if (typeof value === 'function') {
return domEach(this, function(i, el) {
setAttr(el, name, value.call(el, i, el.attribs[name]));
});
}
return domEach(this, function(i, el) {
if (!isTag(el)) return;
if (typeof name === 'object') {
_.each(name, function(name, key) {
el.attribs[key] = name+'';
});
} else {
setAttr(el, name, value);
}
});
}
return getAttr(this[0], name);
};
var setData = function(el, name, value) {
if (typeof name === 'object') return _.extend(el.data, name);
if (typeof name === 'string' && value !== undefined) {
el.data[name] = value;
} else if (typeof name === 'object') {
_.exend(el.data, name);
}
};
// Read the specified attribute from the equivalent HTML5 `data-*` attribute,
// and (if present) cache the value in the node's internal data store. If no
// attribute name is specified, read *all* HTML5 `data-*` attributes in this
// manner.
var readData = function(el, name) {
var readAll = arguments.length === 1;
var domNames, domName, jsNames, jsName, value, idx, length;
if (readAll) {
domNames = Object.keys(el.attribs).filter(function(attrName) {
return attrName.slice(0, dataAttrPrefix.length) === dataAttrPrefix;
});
jsNames = domNames.map(function(domName) {
return camelCase(domName.slice(dataAttrPrefix.length));
});
} else {
domNames = [dataAttrPrefix + cssCase(name)];
jsNames = [name];
}
for (idx = 0, length = domNames.length; idx < length; ++idx) {
domName = domNames[idx];
jsName = jsNames[idx];
if (hasOwn.call(el.attribs, domName)) {
value = el.attribs[domName];
if (hasOwn.call(primitives, value)) {
value = primitives[value];
} else if (value === String(Number(value))) {
value = Number(value);
} else if (rbrace.test(value)) {
value = JSON.parse(value);
}
el.data[jsName] = value;
}
}
return readAll ? el.data : value;
};
var data = exports.data = function(name, value) {
var elem = this[0];
if (!elem || !isTag(elem)) return;
if (!elem.data) {
elem.data = {};
}
// Return the entire data object if no data specified
if (!name) {
return readData(elem);
}
// Set the value (with attr map support)
if (typeof name === 'object' || value !== undefined) {
domEach(this, function(i, el) {
setData(el, name, value);
});
return this;
} else if (hasOwn.call(elem.data, name)) {
return elem.data[name];
}
return readData(elem, name);
};
/**
* Get the value of an element
*/
var val = exports.val = function(value) {
var querying = arguments.length === 0,
element = this[0];
if(!element) return;
switch (element.name) {
case 'textarea':
return this.text(value);
case 'input':
switch (this.attr('type')) {
case 'radio':
var queryString = 'input[type=radio][name=' + this.attr('name') + ']:checked';
var parentEl, root;
// Go up until we hit a form or root
parentEl = this.closest('form');
if (parentEl.length === 0) {
root = (this.parents().last()[0] || this[0]).root;
parentEl = this._make(root);
}
if (querying) {
return parentEl.find(queryString).attr('value');
} else {
parentEl.find(':checked').removeAttr('checked');
parentEl.find('input[type=radio][value="' + value + '"]').attr('checked', '');
return this;
}
break;
default:
return this.attr('value', value);
}
return;
case 'select':
var option = this.find('option:selected'),
returnValue;
if (option === undefined) return undefined;
if (!querying) {
if (!this.attr().hasOwnProperty('multiple') && typeof value == 'object') {
return this;
}
if (typeof value != 'object') {
value = [value];
}
this.find('option').removeAttr('selected');
for (var i = 0; i < value.length; i++) {
this.find('option[value="' + value[i] + '"]').attr('selected', '');
}
return this;
}
returnValue = option.attr('value');
if (this.attr().hasOwnProperty('multiple')) {
returnValue = [];
domEach(option, function(i, el) {
returnValue.push(el.attribs.value);
});
}
return returnValue;
case 'option':
if (!querying) {
this.attr('value', value);
return this;
}
return this.attr('value');
}
};
/**
* Remove an attribute
*/
var removeAttribute = function(elem, name) {
if (!elem.attribs || !hasOwn.call(elem.attribs, name))
return;
if (name === elem.attribs[name] && rboolean.test(elem.attribs[name]))
elem.attribs[name] = false;
else
delete elem.attribs[name];
};
var removeAttr = exports.removeAttr = function(name) {
domEach(this, function(i, elem) {
removeAttribute(elem, name);
});
return this;
};
var hasClass = exports.hasClass = function(className) {
return _.any(this, function(elem) {
var attrs = elem.attribs,
clazz = attrs && attrs['class'],
idx = -1,
end;
if (clazz) {
while ((idx = clazz.indexOf(className, idx+1)) > -1) {
end = idx + className.length;
if ((idx === 0 || rspace.test(clazz[idx-1]))
&& (end === clazz.length || rspace.test(clazz[end]))) {
return true;
}
}
}
});
};
var addClass = exports.addClass = function(value) {
// Support functions
if (typeof value === 'function') {
return domEach(this, function(i, el) {
var className = el.attribs['class'] || '';
addClass.call([el], value.call(el, i, className));
});
}
// Return if no value or not a string or function
if (!value || typeof value !== 'string') return this;
var classNames = value.split(rspace),
numElements = this.length;
for (var i = 0; i < numElements; i++) {
// If selected element isn't a tag, move on
if (!isTag(this[i])) continue;
// If we don't already have classes
var className = getAttr(this[i], 'class'),
numClasses,
setClass;
if (!className) {
setAttr(this[i], 'class', classNames.join(' ').trim());
} else {
setClass = ' ' + className + ' ';
numClasses = classNames.length;
// Check if class already exists
for (var j = 0; j < numClasses; j++) {
var appendClass = classNames[j] + ' ';
if (!~setClass.indexOf(' ' + appendClass))
setClass += appendClass;
}
setAttr(this[i], 'class', setClass.trim());
}
}
return this;
};
var splitClass = function(className) {
return className ? className.trim().split(rspace) : [];
};
var removeClass = exports.removeClass = function(value) {
var classes,
numClasses,
removeAll;
// Handle if value is a function
if (typeof value === 'function') {
return domEach(this, function(i, el) {
removeClass.call([el], value.call(el, i, el.attribs['class'] || ''));
});
}
classes = splitClass(value);
numClasses = classes.length;
removeAll = arguments.length === 0;
return domEach(this, function(i, el) {
if (!isTag(el)) return;
if (removeAll) {
// Short circuit the remove all case as this is the nice one
el.attribs.class = '';
} else {
var elClasses = splitClass(el.attribs.class),
index,
changed;
for (var j = 0; j < numClasses; j++) {
index = elClasses.indexOf(classes[j]);
if (index >= 0) {
elClasses.splice(index, 1);
changed = true;
// We have to do another pass to ensure that there are not duplicate
// classes listed
j--;
}
}
if (changed) {
el.attribs.class = elClasses.join(' ');
}
}
});
};
var toggleClass = exports.toggleClass = function(value, stateVal) {
// Support functions
if (typeof value === 'function') {
return domEach(this, function(i, el) {
toggleClass.call([el], value.call(el, i, el.attribs['class'] || '', stateVal), stateVal);
});
}
// Return if no value or not a string or function
if (!value || typeof value !== 'string') return this;
var classNames = value.split(rspace),
numClasses = classNames.length,
state = typeof stateVal === 'boolean' ? stateVal ? 1 : -1 : 0,
numElements = this.length,
elementClasses,
index;
for (var i = 0; i < numElements; i++) {
// If selected element isn't a tag, move on
if (!isTag(this[i])) continue;
elementClasses = splitClass(this[i].attribs.class);
// Check if class already exists
for (var j = 0; j < numClasses; j++) {
// Check if the class name is currently defined
index = elementClasses.indexOf(classNames[j]);
// Add if stateValue === true or we are toggling and there is no value
if (state >= 0 && index < 0) {
elementClasses.push(classNames[j]);
} else if (state <= 0 && index >= 0) {
// Otherwise remove but only if the item exists
elementClasses.splice(index, 1);
}
}
this[i].attribs.class = elementClasses.join(' ');
}
return this;
};
var is = exports.is = function (selector) {
if (selector) {
return this.filter(selector).length > 0;
}
return false;
};