diff --git a/package-lock.json b/package-lock.json index 1472752a1..600a23767 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1280,6 +1280,11 @@ "integrity": "sha512-JidxKzqyjQP4DSuSHpATn9TovdmzWZAUSDyl6h1D03KnkoDJXXJQ78bd+9fPNVhywVrRUQM6Fodv0xvhZJxZdg==", "dev": true }, + "@fczbkk/power-set-generator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@fczbkk/power-set-generator/-/power-set-generator-1.0.0.tgz", + "integrity": "sha512-sJ5wUPTtGxKSxIuEqzhbMWDW+xbAsy1ovbzwf/MkM5XAgyDWu4V87AdKHjFA2WHnuD1GHywq1QILDfK+u/W8dw==" + }, "@types/component-emitter": { "version": "1.2.10", "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz", diff --git a/package.json b/package.json index a45b57225..f4ba08f99 100644 --- a/package.json +++ b/package.json @@ -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" } diff --git a/src/constants.js b/src/constants.js index 042361406..cfeab521f 100644 --- a/src/constants.js +++ b/src/constants.js @@ -1,4 +1,5 @@ export const DESCENDANT_OPERATOR = ' > '; +export const CHILD_OPERATOR = ' '; /** * Constructs default options with proper root node for given element. diff --git a/src/index.js b/src/index.js index 14c12b12e..31a9edead 100644 --- a/src/index.js +++ b/src/index.js @@ -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. @@ -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; diff --git a/src/utilities-dom.js b/src/utilities-dom.js index 278282521..3a8216534 100644 --- a/src/utilities-dom.js +++ b/src/utilities-dom.js @@ -32,13 +32,21 @@ export function testSelectorOnChildren (element, selector, root = document) { * @return {Array.} */ 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.} + */ +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; } /** diff --git a/src/utilities-selectors.js b/src/utilities-selectors.js index ca6e80b1e..c9124db12 100644 --- a/src/utilities-selectors.js +++ b/src/utilities-selectors.js @@ -1,3 +1,4 @@ +import { generateElementSelectorCandidates } from './index.js' import {getNthChildSelector} from './selector-nth-child'; import {SELECTOR_PATTERN} from './constants'; import cartesian from 'cartesian'; @@ -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'; @@ -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 @@ -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 +} diff --git a/test/complex.spec.js b/test/complex.spec.js index 364bb01d2..c1f55f5ee 100644 --- a/test/complex.spec.js +++ b/test/complex.spec.js @@ -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'; diff --git a/test/index.spec.js b/test/index.spec.js index d23b9885e..c368a4684 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -27,7 +27,7 @@ describe('CssSelectorGenerator', function () { '
'; const element = root.firstChild.firstChild.firstChild; const result = getCssSelector(element, {root}); - assert.equal(result, '#aaa > div > .aaa'); + assert.equal(result, '#aaa .aaa'); }); }); diff --git a/test/options-include-tag.spec.js b/test/options-include-tag.spec.js index 7f30bc5c8..e053f0e49 100644 --- a/test/options-include-tag.spec.js +++ b/test/options-include-tag.spec.js @@ -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'); }); });