Skip to content

Commit

Permalink
feat: add support for child selectors
Browse files Browse the repository at this point in the history
  • Loading branch information
fczbkk committed Mar 20, 2021
1 parent 2d61cb1 commit ac44213
Show file tree
Hide file tree
Showing 9 changed files with 97 additions and 20 deletions.
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
},
"homepage": "https://github.com/fczbkk/css-selector-generator/",
"dependencies": {
"@fczbkk/power-set-generator": "^1.0.0",
"cartesian": "^1.0.1",
"iselement": "^1.1.4"
}
Expand Down
1 change: 1 addition & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const DESCENDANT_OPERATOR = ' > ';
export const CHILD_OPERATOR = ' ';

/**
* Constructs default options with proper root node for given element.
Expand Down
42 changes: 29 additions & 13 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
import {getUniqueSelectorWithinParent} from './utilities-selectors';
import {
getAllSelectors, getClosestIdentifiableParent
} from './utilities-selectors'
import {getFallbackSelector} from './selector-fallback';
import {DESCENDANT_OPERATOR} from './constants';
import { CHILD_OPERATOR, DESCENDANT_OPERATOR } from './constants'
import {sanitizeOptions} from './utilities-options';
import {getParents, testSelector} from './utilities-dom';

export function * generateElementSelectorCandidates (element, root, options) {
const selectorCandidates = getAllSelectors(element, root, options)
for (const selectorCandidate of selectorCandidates) {
yield CHILD_OPERATOR + selectorCandidate
}
if (root === element.parentNode) {
for (const selectorCandidate of selectorCandidates) {
yield DESCENDANT_OPERATOR + selectorCandidate
}
}
}

/**
* Generates unique CSS selector for an element.
Expand All @@ -11,21 +24,24 @@ import {getParents, testSelector} from './utilities-dom';
* @return {string}
*/
export function getCssSelector (element, custom_options = {}) {
let counter = 0
const options = sanitizeOptions(element, custom_options);
const parents = getParents(element, options.root);
const result = [];
let partialSelector = ''
let currentRoot = options.root

// try to find optimized selector
for (let i = 0; i < parents.length; i++) {
result.unshift(getUniqueSelectorWithinParent(parents[i], options));
const selector = result.join(DESCENDANT_OPERATOR);
if (testSelector(element, selector, options.root)) {
return selector;
let closestIdentifiableParent = getClosestIdentifiableParent(element, currentRoot, partialSelector, options)
while (closestIdentifiableParent) {
const {foundElement, selector} = closestIdentifiableParent
if (foundElement === element) {
return selector
}
currentRoot = foundElement
partialSelector = selector
closestIdentifiableParent = getClosestIdentifiableParent(element, currentRoot, partialSelector, options)
counter++
}

// use nth-child selector chain to root as fallback
return getFallbackSelector(element, options.root);
return getFallbackSelector(element);
}

export default getCssSelector;
Expand Down
14 changes: 11 additions & 3 deletions src/utilities-dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,21 @@ export function testSelectorOnChildren (element, selector, root = document) {
* @return {Array.<Element>}
*/
export function getParents (element, root = getRootNode(element)) {
const result = [];
return [...generateParents(element, root)]
}

/**
* Generate all parent elements of the element.
* @param {Element} element
* @param {Element} root
* @return {Array.<Element>}
*/
export function * generateParents (element, root = getRootNode(element)) {
let parent = element;
while (isElement(parent) && parent !== root) {
result.push(parent);
yield parent
parent = parent.parentElement;
}
return result;
}

/**
Expand Down
48 changes: 47 additions & 1 deletion src/utilities-selectors.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { generateElementSelectorCandidates } from './index.js'
import {getNthChildSelector} from './selector-nth-child';
import {SELECTOR_PATTERN} from './constants';
import cartesian from 'cartesian';
Expand All @@ -6,7 +7,11 @@ import {
flattenArray,
getCombinations,
} from './utilities-data';
import {testSelectorOnChildren} from './utilities-dom';
import {
generateParents,
testSelector,
testSelectorOnChildren
} from './utilities-dom'
import {getTagSelector} from './selector-tag';
import {getIdSelector} from './selector-id';
import {getClassSelectors} from './selector-class';
Expand Down Expand Up @@ -113,6 +118,27 @@ export function getUniqueSelectorWithinParent (element, options) {
return '*';
}

export function getAllSelectors (element, root, options) {
const selectors_list = getSelectorsList(element, options);
const type_combinations = getTypeCombinations(selectors_list, options);
const all_selectors = flattenArray(type_combinations);
return [...new Set(all_selectors)];
}

export function getUniqueChildSelector (element, root = document, options) {
const selectors_list = getSelectorsList(element, options);
const type_combinations = getTypeCombinations(selectors_list, options);
const all_selectors = flattenArray(type_combinations);

for (let i = 0; i < all_selectors.length; i++) {
const selector = all_selectors[i];
if (testSelector(element, selector, root)) {
return selector;
}
}
return null
}

/**
* Creates object containing all selector types and their potential values.
* @param {Element} element
Expand Down Expand Up @@ -257,3 +283,23 @@ export function constructSelector (selector_data = {}) {
.join('');
}

export function getSelectorWithinRoot (element, root, rootSelector = '', options) {
const candidatesGenerator = generateElementSelectorCandidates(element, options.root, options)
for (const candidateSelector of candidatesGenerator) {
const attemptSelector = (rootSelector + candidateSelector).trim()
if (testSelector(element, attemptSelector, options.root)) {
return attemptSelector
}
}
return null
}

export function getClosestIdentifiableParent (element, root, rootSelector = '', options) {
for (const currentElement of generateParents(element, root)) {
const result = getSelectorWithinRoot(currentElement, root, rootSelector, options)
if (result) {
return {foundElement: currentElement, selector: result}
}
}
return null
}
2 changes: 1 addition & 1 deletion test/complex.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {assert} from 'chai';
import {getCssSelector} from './../src/index';
import { getCssSelector } from './../src/index'
import {testSelector} from '../src/utilities-dom';
import html_code from './html/complex.html';

Expand Down
2 changes: 1 addition & 1 deletion test/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ describe('CssSelectorGenerator', function () {
'<div id="aaa" class="aaa"><div><div class="aaa"></div></div></div>';
const element = root.firstChild.firstChild.firstChild;
const result = getCssSelector(element, {root});
assert.equal(result, '#aaa > div > .aaa');
assert.equal(result, '#aaa .aaa');
});

});
Expand Down
2 changes: 1 addition & 1 deletion test/options-include-tag.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ describe('options: includeTag', function () {
includeTag: true,
selectors: ['class']
});
assert.equal(result, 'div.aaa > div.aaa');
assert.equal(result, 'div.bbb div.aaa');
});

});

0 comments on commit ac44213

Please sign in to comment.