Skip to content

Commit

Permalink
feat: added prefix_tag option
Browse files Browse the repository at this point in the history
  • Loading branch information
kdmurthy committed Oct 17, 2018
1 parent 2452adb commit 649bc14
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 25 deletions.
58 changes: 42 additions & 16 deletions build/css-selector-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

CssSelectorGenerator = (function() {
CssSelectorGenerator.prototype.default_options = {
selectors: ['id', 'class', 'tag', 'nthchild']
selectors: ['id', 'class', 'tag', 'nthchild'],
prefix_tag: false,
log: false
};

function CssSelectorGenerator(options) {
Expand Down Expand Up @@ -69,10 +71,11 @@
};

CssSelectorGenerator.prototype.getIdSelector = function(element) {
var id, sanitized_id;
var id, prefix, sanitized_id;
prefix = this.options.prefix_tag ? this.getTagSelector(element) : '';
id = element.getAttribute('id');
if ((id != null) && (id !== '') && !(/\s/.exec(id)) && !(/^\d/.exec(id))) {
sanitized_id = "#" + (this.sanitizeItem(id));
sanitized_id = prefix + ("#" + (this.sanitizeItem(id)));
if (element.ownerDocument.querySelectorAll(sanitized_id).length === 1) {
return sanitized_id;
}
Expand Down Expand Up @@ -118,8 +121,9 @@
};

CssSelectorGenerator.prototype.getNthChildSelector = function(element) {
var counter, k, len, parent_element, sibling, siblings;
var counter, k, len, parent_element, prefix, sibling, siblings;
parent_element = element.parentNode;
prefix = this.options.prefix_tag ? this.getTagSelector(element) : '';
if (parent_element != null) {
counter = 0;
siblings = parent_element.childNodes;
Expand All @@ -128,7 +132,7 @@
if (this.isElement(sibling)) {
counter++;
if (sibling === element) {
return ":nth-child(" + counter + ")";
return prefix + (":nth-child(" + counter + ")");
}
}
}
Expand All @@ -150,31 +154,53 @@

CssSelectorGenerator.prototype.testUniqueness = function(element, selector) {
var found_elements, parent;
if (this.options.log) {
console.log("selector", element, selector);
}
parent = element.parentNode;
found_elements = parent.querySelectorAll(selector);
return found_elements.length === 1 && found_elements[0] === element;
};

CssSelectorGenerator.prototype.testCombinations = function(element, items, tag) {
var item, k, l, len, len1, ref, ref1;
ref = this.getCombinations(items);
for (k = 0, len = ref.length; k < len; k++) {
item = ref[k];
if (this.testUniqueness(element, item)) {
return item;
}
var item, k, l, len, len1, len2, len3, m, n, ref, ref1, ref2, ref3;
if (tag == null) {
tag = this.getTagSelector(element);
}
if (tag != null) {
ref1 = items.map(function(item) {
return tag + item;
});
if (!this.options.prefix_tag) {
ref = this.getCombinations(items);
for (k = 0, len = ref.length; k < len; k++) {
item = ref[k];
if (this.testSelector(element, item)) {
return item;
}
}
ref1 = this.getCombinations(items);
for (l = 0, len1 = ref1.length; l < len1; l++) {
item = ref1[l];
if (this.testUniqueness(element, item)) {
return item;
}
}
}
ref2 = this.getCombinations(items).map(function(item) {
return tag + item;
});
for (m = 0, len2 = ref2.length; m < len2; m++) {
item = ref2[m];
if (this.testSelector(element, item)) {
return item;
}
}
ref3 = this.getCombinations(items).map(function(item) {
return tag + item;
});
for (n = 0, len3 = ref3.length; n < len3; n++) {
item = ref3[n];
if (this.testUniqueness(element, item)) {
return item;
}
}
return null;
};

Expand Down
28 changes: 20 additions & 8 deletions src/css-selector-generator.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ class CssSelectorGenerator

default_options:
# choose from 'tag', 'id', 'class', 'nthchild', 'attribute'
selectors: ['id', 'class', 'tag', 'nthchild']
selectors: ['id', 'class', 'tag', 'nthchild'],
prefix_tag: false, log: false

constructor: (options = {}) ->
@options = {}
Expand Down Expand Up @@ -45,6 +46,7 @@ class CssSelectorGenerator


getIdSelector: (element) ->
prefix = if @options.prefix_tag then @getTagSelector element else ''
id = element.getAttribute 'id'

# ID must... exist, not to be empty and not to contain whitespace
Expand All @@ -58,7 +60,7 @@ class CssSelectorGenerator
# ...not start with a number
not (/^\d/.exec id)
)
sanitized_id = "##{@sanitizeItem id}"
sanitized_id = prefix + "##{@sanitizeItem id}"
# ID must match single element
if element.ownerDocument.querySelectorAll(sanitized_id).length is 1
return sanitized_id
Expand Down Expand Up @@ -88,13 +90,14 @@ class CssSelectorGenerator

getNthChildSelector: (element) ->
parent_element = element.parentNode
prefix = if @options.prefix_tag then @getTagSelector element else ''
if parent_element?
counter = 0
siblings = parent_element.childNodes
for sibling in siblings
if @isElement sibling
counter++
return ":nth-child(#{counter})" if sibling is element
return prefix + ":nth-child(#{counter})" if sibling is element
null

testSelector: (element, selector) ->
Expand All @@ -106,21 +109,30 @@ class CssSelectorGenerator


testUniqueness: (element, selector) ->
if @options.log
console.log("selector", element, selector)
parent = element.parentNode
found_elements = parent.querySelectorAll selector
found_elements.length is 1 and found_elements[0] is element


# helper function that tests all combinations for uniqueness
testCombinations: (element, items, tag) ->
for item in @getCombinations items
return item if @testUniqueness element, item
if not tag?
tag = @getTagSelector element

# if tag selector is enabled, try attaching it
if tag?
for item in (items.map (item) -> tag + item)
if not @options.prefix_tag
for item in @getCombinations items
return item if @testSelector element, item
for item in @getCombinations items
return item if @testUniqueness element, item

# if tag selector is enabled, try attaching it
for item in (@getCombinations(items).map (item) -> tag + item)
return item if @testSelector element, item
for item in (@getCombinations(items).map (item) -> tag + item)
return item if @testUniqueness element, item

return null


Expand Down
74 changes: 73 additions & 1 deletion test/src/css-selector-generator.spec.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,12 @@ describe 'CSS Selector Generator', ->
expect(x.getIdSelector elm).toBe '#linkZero'
expect(x.getIdSelector root).toBe null

it 'should get ID selector for an element with tag', ->
x.setOptions prefix_tag: true
elm = root.querySelector '#linkZero'
expect(x.getIdSelector elm).toBe 'a#linkZero'
expect(x.getIdSelector root).toBe null

it 'should escape special characters in ID selector', ->
special_characters = '*+-./;'
for special_character in special_characters.split ''
Expand Down Expand Up @@ -203,6 +209,11 @@ describe 'CSS Selector Generator', ->
root.innerHTML = '<div class="aaa"></div>'
expect(x.getSelector root.firstChild).toEqual '.aaa'

it 'should get element by class selector with tag', ->
x.setOptions selectors: ['class'], prefix_tag: true
root.innerHTML = '<div class="aaa"></div>'
expect(x.getSelector root.firstChild).toEqual 'div.aaa'

it 'should combine class selector with tag selector if needed', ->
x.setOptions selectors: ['tag', 'class']
root.innerHTML = '
Expand All @@ -217,6 +228,11 @@ describe 'CSS Selector Generator', ->
root.innerHTML = '<p class="aaa bbb"></p><p class="aaa ccc"></p>'
expect(x.getSelector root.firstChild).toEqual '.bbb'

it 'should use single unique class when applicable with tag', ->
x.setOptions selectors: ['class'], prefix_tag: true
root.innerHTML = '<p class="aaa bbb"></p><p class="aaa ccc"></p>'
expect(x.getSelector root.firstChild).toEqual 'p.bbb'

it 'should use combination of classes when applicable', ->
x.setOptions selectors: ['class']
root.innerHTML = '
Expand All @@ -226,6 +242,15 @@ describe 'CSS Selector Generator', ->
'
expect(x.getSelector root.firstChild).toEqual '.aaa.bbb'

it 'should use combination of classes when applicable with tag', ->
x.setOptions selectors: ['class' ], prefix_tag: true
root.innerHTML = '
<div class="aaa bbb"></div>
<div class="aaa ccc"></div>
<div class="bbb ccc"></div>
'
expect(x.getSelector root.firstChild).toEqual 'div.aaa.bbb'

describe 'attribute', ->

it 'should get attribute selectors for an element', ->
Expand All @@ -244,11 +269,23 @@ describe 'CSS Selector Generator', ->
result = x.getSelector root.firstChild
expect(result).toEqual '[rel=aaa]'

it 'should use attribute selector when enabled with tag', ->
x.setOptions selectors: ['tag', 'id', 'class',
'attribute', 'nthchild'], prefix_tag: true
root.innerHTML = '<a rel="aaa"></a><a rel="bbb"></a>'
result = x.getSelector root.firstChild
expect(result).toEqual 'a[rel=aaa]'

it 'should get element by attribute selector', ->
x.setOptions selectors: ['attribute']
root.innerHTML = '<a href="aaa"></a>'
expect(x.getSelector root.firstChild).toEqual '[href=aaa]'

it 'should get element by attribute selector with tag', ->
x.setOptions selectors: ['attribute'], prefix_tag: true
root.innerHTML = '<a href="aaa"></a>'
expect(x.getSelector root.firstChild).toEqual 'a[href=aaa]'

it 'should combine attribute selector with tag selector if needed', ->
x.setOptions selectors: ['attribute', 'tag']
root.innerHTML = '
Expand All @@ -266,7 +303,15 @@ describe 'CSS Selector Generator', ->
'
expect(x.getSelector root.firstChild).toEqual '[rel=bbb]'

it 'should use combination of classes when applicable', ->
it 'should use single unique attribute when applicable with tag', ->
x.setOptions selectors: ['attribute'], prefix_tag: true
root.innerHTML = '
<a href="aaa" rel="bbb"></a>
<a href="aaa" rel="ccc"></a>
'
expect(x.getSelector root.firstChild).toEqual 'a[rel=bbb]'

it 'should use combination of attributes when applicable', ->
x.setOptions selectors: ['attribute']
root.innerHTML = '
<a href="aaa" rel="aaa"></a>
Expand All @@ -275,13 +320,40 @@ describe 'CSS Selector Generator', ->
'
expect(x.getSelector root.firstChild).toEqual '[href=aaa][rel=aaa]'

it 'should use combination of attributes when applicable with tag', ->
x.setOptions selectors: ['attribute'], prefix_tag: true
root.innerHTML = '
<a href="aaa" rel="aaa"></a>
<a href="aaa" rel="xxx"></a>
<a href="xxx" rel="aaa"></a>
'
expect(x.getSelector root.firstChild).toEqual 'a[href=aaa][rel=aaa]'

it 'should use unique attribute from document', ->
x.setOptions selectors: ['attribute']
x.setOptions log:true
root.innerHTML = '
<div data-id="a1"><a id="aaa" href="aaa" rel="bbb">link1</a></div>
<div><a href="aaa" rel="aaa">link2</a></div>
'

elm = root.querySelector '#aaa'
expect(x.getSelector elm).
toEqual '[rel=bbb]'

describe 'n-th child', ->

it 'should get n-th child selector for an element', ->
elm = root.querySelector '#linkZero'
result = x.getNthChildSelector elm
expect(result).toBe ':nth-child(7)'

it 'should get n-th child selector for an element with tag', ->
x.setOptions prefix_tag: true
elm = root.querySelector '#linkZero'
result = x.getNthChildSelector elm
expect(result).toBe 'a:nth-child(7)'


describe 'selector test', ->
elm = null
Expand Down

0 comments on commit 649bc14

Please sign in to comment.