@mariusandra/simmerjs
Version:
A pure Javascript reverse CSS selector engine which calculates a DOM element's unique CSS selector on the current page.
140 lines (109 loc) • 6.57 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = createSimmer;
var _queryEngine = _interopRequireWildcard(require("./queryEngine"));
var _methods = _interopRequireDefault(require("./methods"));
var _validateSelector = _interopRequireDefault(require("./validateSelector"));
var _convertSelectorStateIntoCSSSelector = _interopRequireDefault(require("./convertSelectorStateIntoCSSSelector"));
var _parser = _interopRequireDefault(require("./parser"));
var _stackHierarchy = _interopRequireDefault(require("./stackHierarchy"));
var _configuration = require("./configuration");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function createSimmer() {
let windowScope = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : window;
let customConfig = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
let customQuery = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
const config = (0, _configuration.configure)(customConfig);
const query = customQuery || (0, _queryEngine.default)(windowScope, config.queryEngine);
/**
* Handle errors in accordance with what is specified in the configuration
* @param {object/string} ex. The exception object or message
* @param {object} element. The element Simmer was asked to process
*/
function onError(ex, element) {
// handle error
if (config.errorHandling === true) {
throw ex;
}
if (typeof config.errorHandling === 'function') {
config.errorHandling(ex, element);
}
} // Initialize the Simmer object and set it over the reference on the window
/**
* The main Simmer action - parses an element on the page to produce a CSS selector for it.
* This function will be returned into the global Simmer object.
* @param {object} element. A DOM element you wish to create a selector for.
* @example
<code><pre>
var cssSelectorForDonJulio = Simmer(document.getElementByID('DonJulio'));
</pre></code>
*/
const simmer = function (element) {
if (!element) {
// handle error
onError.call(simmer, new Error('Simmer: No element was specified for parsing.'), element);
return null;
} // The parser cycles through a set of parsing methods specified in an order optimal
// for creating as specific as possible a selector
const parser = new _parser.default(_methods.default); // get the element's ancestors
const hierarchy = (0, _stackHierarchy.default)((0, _queryEngine.wrap)(element), config.depth); // initialize the state of the selector
let selectorState = {
// the stack is used to build a layer of selectors, each layer coresponding to a specific element in the heirarchy
// for each level we create a private stack of properties, so that we can then merge them
// comfortably and allow all methods to see the level at which existing properties have been set
stack: Array(hierarchy.length).fill().map(() => []),
// follow the current specificity level of the selector - the higher the better
specificity: 0
};
const validator = (0, _validateSelector.default)(element, config, query, onError); // cycle through the available parsing methods and while we still have yet to find the requested element's one-to-one selector
// we keep calling the methods until we are either satisfied or run out of methods
while (!parser.finished() && !selectorState.verified) {
try {
selectorState = parser.next(hierarchy, selectorState, validator, config, query); // if we have reached a satisfactory level of specificity, try the selector, perhaps we have found our selector?
if (selectorState.specificity >= config.specificityThreshold && !selectorState.verified) {
selectorState.verified = validator(selectorState);
}
} catch (ex) {
// handle error
onError.call(simmer, ex, element);
}
} // if we were not able to produce a one-to-one selector, return false
if (selectorState.verified === undefined || selectorState.specificity < config.specificityThreshold) {
// if it is undefined then verfication has never been run!
// try and verify, and if verification fails - return false
// if it is false and the specificity is too low to actually try and find the element in the first place, then we may simply have not run
// an up to date verification - try again
selectorState.verified = validator(selectorState);
}
if (!selectorState.verified) {
return null;
}
if (selectorState.verificationDepth) {
return (0, _convertSelectorStateIntoCSSSelector.default)(selectorState, selectorState.verificationDepth);
}
return (0, _convertSelectorStateIntoCSSSelector.default)(selectorState);
};
/**
* Get/Set the configuration for the Simmer object
* @param config (Object) A configuration object with any of the properties tweeked (none/depth/minimumSpecificity)
* @example
<code><pre>
configuration({
depth: 3
});
</pre></code>
*/
simmer.configure = function () {
let configValues = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
let scope = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : windowScope;
const newConfig = (0, _configuration.configure)({ ...config,
...configValues
});
return createSimmer(scope, newConfig, (0, _queryEngine.default)(scope, newConfig.queryEngine));
};
return simmer;
}