├── .gitignore
├── .travis.yml
├── test-main.js
├── package.json
├── README.md
├── karma.conf.js
├── LICENSE
├── test
└── CSSselectorSimple.spec.js
└── lib
└── CssSelector.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | node_modules
3 |
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "0.10"
4 | before_script:
5 | - export DISPLAY=:99.0
6 | - sh -e /etc/init.d/xvfb start
7 |
8 |
--------------------------------------------------------------------------------
/test-main.js:
--------------------------------------------------------------------------------
1 | var allTestFiles = [];
2 | var TEST_REGEXP = /(spec|test)\.js$/i;
3 |
4 | var pathToModule = function(path) {
5 | return path.replace(/^\/base\//, '').replace(/\.js$/, '');
6 | };
7 |
8 | Object.keys(window.__karma__.files).forEach(function(file) {
9 | if (TEST_REGEXP.test(file)) {
10 | // Normalize paths to RequireJS module names.
11 | allTestFiles.push(pathToModule(file));
12 | }
13 | });
14 |
15 | require.config({
16 | // Karma serves files under /base, which is the basePath from your config file
17 | baseUrl: '/base',
18 |
19 | // dynamically load all test files
20 | deps: allTestFiles,
21 |
22 | // we have to kickoff jasmine, as it is asynchronous
23 | callback: window.__karma__.start
24 | });
25 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "css-selector",
3 | "version": "v0.1.0",
4 | "description": "Retrieves CSS selector for a given element in DOM.",
5 | "homepage": "https://github.com/martinsbalodis/css-selector",
6 | "license": "LGPL-3.0+",
7 | "repository": {
8 | "type": "git",
9 | "url": "https://github.com/martinsbalodis/css-selector"
10 | },
11 | "author": {
12 | "name": "Martins Balodis",
13 | "email": "martins256@gmail.com"
14 | },
15 | "scripts": {
16 | "test": "./node_modules/karma/bin/karma start --browsers Firefox --single-run"
17 | },
18 | "dependencies": {
19 | "jquery": "~2.1.1"
20 | },
21 | "devDependencies": {
22 | "karma-chrome-launcher": "^0.1.4",
23 | "karma-firefox-launcher": "~0.1",
24 | "karma-jasmine": "^0.1.5",
25 | "karma-requirejs": "^0.2.2",
26 | "requirejs": "^2.1.14"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CSS Selector
2 | [](https://travis-ci.org/martinsbalodis/css-selector)
3 |
4 | CSS selector can be used to retrieve CSS selector for a given element in DOM. The resulting selector will be optimized to be as short as possible.
5 | CSS selector can be retrieved also for multiple elements. In such case the resulting selector might be a much wider CSS selector which will point to similar elements.
6 |
7 | ## Usage
8 | ```javascript
9 | var selector = new CssSelector({
10 | parent: document,
11 | enableResultStripping: true,
12 | ignoredTags: ['font'],
13 | enableSmartTableSelector: true,
14 | allowMultipleSelectors: false,
15 | query: jQuery,
16 | ignoredClasses: [
17 | 'my-class'
18 | ]
19 | });
20 | var elements = document.getElementsByClassName('my-class');
21 | var result_selector = selector.getCssSelector(elements);
22 | // #id div:nth-of-type(1) .another-class
23 | ```
24 |
25 | ## Features
26 |
27 | - Tag name selector
28 | - Id selector
29 | - Class name selector
30 | - nth-of-child selector
31 | - Direct Child selector (a > b)
32 | - Smart table selector
33 |
34 | ### Smart table selector
35 | For example you have a table like you can see below and you need to get the CSS selector for `
banana
`. The selector could be retrieved with `nth-of-child` selector. But in this case the resulting selector wouldn't be a very strong one. Using smart table you would get CSS selector like this `tr:contains('title:') td:nth-of-type(2)`.
36 | ```html
37 |
38 |
title:
banana
39 |
color:
yellow
40 |
41 | ```
42 |
43 | ## Contributions
44 | Please include tests for added features.
45 |
46 | ## License
47 | LGPLv3
48 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | // Generated on Thu Jun 19 2014 20:42:33 GMT+0300 (EEST)
3 |
4 | module.exports = function(config) {
5 | config.set({
6 |
7 | // base path that will be used to resolve all patterns (eg. files, exclude)
8 | basePath: '',
9 |
10 |
11 | // frameworks to use
12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
13 | frameworks: ['jasmine', 'requirejs'],
14 |
15 |
16 | // list of files / patterns to load in the browser
17 | files: [
18 | 'test-main.js',
19 | {pattern: 'node_modules/jquery/dist/jquery.js', included:true},
20 | {pattern: 'lib/CssSelector.js', included:true},
21 | {pattern: 'test/*spec.js', included: false}
22 | ],
23 |
24 |
25 | // list of files to exclude
26 | exclude: [
27 |
28 | ],
29 |
30 |
31 | // preprocess matching files before serving them to the browser
32 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
33 | preprocessors: {
34 |
35 | },
36 |
37 |
38 | // test results reporter to use
39 | // possible values: 'dots', 'progress'
40 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter
41 | reporters: ['dots'],
42 |
43 |
44 | // web server port
45 | port: 9876,
46 |
47 |
48 | // enable / disable colors in the output (reporters and logs)
49 | colors: true,
50 |
51 |
52 | // level of logging
53 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
54 | logLevel: config.LOG_WARN,
55 |
56 |
57 | // enable / disable watching file and executing test whenever any file changes
58 | autoWatch: false,
59 |
60 |
61 | // start these browsers
62 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
63 | browsers: ['Chrome'],
64 |
65 |
66 | // Continuous Integration mode
67 | // if true, Karma captures browsers, runs the test and exits
68 | singleRun: false
69 | });
70 | };
71 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------
/test/CSSselectorSimple.spec.js:
--------------------------------------------------------------------------------
1 | var selector, $el;
2 | jQuery("body").append("");
3 |
4 | describe("CSS Selector Simple", function () {
5 |
6 | beforeEach(function () {
7 |
8 | $el = jQuery("#tests").html("");
9 | selector = new CssSelector({
10 | parent: jQuery('#tests')[0],
11 | enableResultStripping: false,
12 | ignoredClasses: [
13 | 'test-ignore-tags',
14 | 'test-multi-element-n',
15 | 'test-skip-top-n'
16 | ]
17 | });
18 | });
19 |
20 | it("should be able to select one element", function () {
21 |
22 | $el.append('
');
23 |
24 | var element = document.getElementsByName('test-simple-element')[0];
25 | var css_selector = selector.getCssSelector([element]);
26 |
27 | expect(css_selector).toBe("div:nth-of-type(1) > a:nth-of-type(1)");
28 | });
29 |
30 | it("should be able to select multiple elements", function () {
31 |
32 | $el.append('
');
33 |
34 | var elements = document.getElementsByName('test-milti-element');
35 | var css_selector = selector.getCssSelector(elements);
36 |
37 | expect(css_selector).toBe("div:nth-of-type(1) > span > a:nth-of-type(1)");
38 | });
39 |
40 | it("should be able to select multiple elements n+", function () {
41 |
42 | $el.append('
');
43 |
44 | var elements = jQuery('.test-multi-element-n');
45 | var css_selector = selector.getCssSelector(elements);
46 |
47 |
48 | expect(css_selector).toBe("div:nth-of-type(1) > span:nth-of-type(n+2) > a:nth-of-type(1)");
49 | });
50 |
51 | it("should be able to ignore tags", function () {
52 |
53 | $el.append('
');
54 | var elements = jQuery('.test-ignore-tags');
55 | var css_selector = selector.getCssSelector(elements);
56 |
57 | expect(css_selector).toBe("table:nth-of-type(1) > tbody:nth-of-type(1) > tr:nth-of-type(1) > td:nth-of-type(1)");
58 | });
59 |
60 | it("should escape colon in tag name, id and class", function () {
61 |
62 | $el.append('');
63 | var elements = jQuery('.colon-test');
64 | var cssSelector = selector.getCssSelector(elements);
65 |
66 | expect(cssSelector).toBe("div\\:test#colon\\:testid.colon-test.colon\\:class:nth-of-type(1)");
67 | expect($(cssSelector).length).toBe(1);
68 | });
69 |
70 | it("should be able to skip elements from top", function () {
71 |
72 | $el.append('
');
73 |
74 | var elements = jQuery('.test-skip-top');
75 | var css_selector = selector.getCssSelector(elements, 2);
76 |
77 | expect(css_selector).toBe("table:nth-of-type(1) > tbody:nth-of-type(1) > tr:nth-of-type(1)");
78 | });
79 |
80 | it("should be able to skip elements from top and use n+1 selectors", function () {
81 |
82 | $el.append('
');
83 |
84 | var elements = jQuery('.test-skip-top-n');
85 | var css_selector = selector.getCssSelector(elements, 2);
86 |
87 | expect(css_selector).toBe("table:nth-of-type(1) > tbody:nth-of-type(1) > tr:nth-of-type(n+2)");
88 | });
89 |
90 | it("should be able to select body", function () {
91 |
92 | var elements = jQuery('body');
93 | selector.parent = jQuery('html')[0];
94 | var css_selector = selector.getCssSelector(elements);
95 |
96 | expect(css_selector).toBe("body");
97 | });
98 |
99 | it("should be able to select html", function () {
100 |
101 | var elements = jQuery('html');
102 | selector.parent = document;
103 | var css_selector = selector.getCssSelector(elements);
104 |
105 | expect(css_selector).toBe("html");
106 | });
107 | });
108 |
109 | describe("CSS Selector Strip", function () {
110 |
111 | beforeEach(function () {
112 |
113 | $el = jQuery("#tests").html("");
114 | selector = new CssSelector({
115 | parent: jQuery('#tests')[0],
116 | ignoredClasses:['do-not-strip-direct-child-test']
117 | });
118 | });
119 |
120 | it("should be able to strip indexes", function () {
121 |
122 | $el.append('');
123 | var element = document.getElementsByName('strip-index-test')[0];
124 | var css_selector = selector.getCssSelector([element]);
125 |
126 | expect(css_selector).toBe("input");
127 | });
128 |
129 | it("should be able to strip ids", function () {
130 |
131 | $el.append('');
132 | var element = document.getElementsByName('strip-id-test')[0];
133 | var css_selector = selector.getCssSelector([element]);
134 |
135 | expect(css_selector).toBe("textarea");
136 | });
137 |
138 | it("should be able to strip classes", function () {
139 |
140 | $el.append('
');
141 | var element = document.getElementsByName('strip-tags-test')[0];
142 | var css_selector = selector.getCssSelector([element]);
143 |
144 | expect(css_selector).toBe("select");
145 | });
146 |
147 | it("should be able to strip whole tags", function () {
148 |
149 | $el.append('
');
150 | var element = document.getElementsByName('strip-classes-test')[0];
151 | var css_selector = selector.getCssSelector([element]);
152 |
153 | expect(css_selector).toBe("span.needed a");
154 | });
155 |
156 | it("should not strip direct child when a subchild exists", function () {
157 |
158 | $el.append('
');
159 | var elements = $('.do-not-strip-direct-child-test');
160 | var css_selector = selector.getCssSelector(elements);
161 |
162 | expect(css_selector).toBe("span > div");
163 | });
164 |
165 | });
166 |
167 | describe("Combine css selectors", function() {
168 |
169 | beforeEach(function () {
170 |
171 | $el = jQuery("#tests").html("");
172 | selector = new CssSelector({
173 | parent: jQuery('#tests')[0],
174 | allowMultipleSelectors: true
175 | });
176 | });
177 |
178 | it("should find elements similar", function() {
179 |
180 | $el.append('');
181 | var span1 = $('.span1', $el)[0];
182 | var span2 = $('.span2', $el)[0];
183 | var result = selector.checkSimilarElements(span1, span2);
184 |
185 | expect(result).toBe(true);
186 | });
187 |
188 | it("should not find elements similar at different deepnesses", function() {
189 |
190 | $el.append('
');
191 | var span1 = $('.span1', $el)[0];
192 | var span2 = $('.span2', $el)[0];
193 | var result = selector.checkSimilarElements(span1, span2);
194 |
195 | expect(result).toBe(false);
196 | });
197 |
198 | it("should group similar elements", function(){
199 |
200 | $el.append('');
201 | var elements = $('span', $el).get();
202 | var result = selector.getElementGroups(elements);
203 |
204 | expect(result.length).toBe(1);
205 | expect(result[0]).toEqual(elements);
206 | });
207 |
208 | it("should not group not similar elements", function(){
209 |
210 | $el.append('
');
211 | var elements = $('span', $el).get();
212 | var result = selector.getElementGroups(elements);
213 |
214 | expect(result.length).toBe(2);
215 | expect(result[0]).toEqual([elements[0]]);
216 | expect(result[1]).toEqual([elements[1]]);
217 | });
218 |
219 | it("should combine two selectors", function() {
220 |
221 | $el.append('');
222 | var elements = $('div, span', $el);
223 | var cssSelector = selector.getCssSelector(elements);
224 |
225 | expect(cssSelector).toBe("div, span");
226 | });
227 |
228 | it("should combine two selectors at different deepnesses", function() {
229 |
230 | $el.append('
');
231 | var elements = $('.div1, span', $el);
232 | var cssSelector = selector.getCssSelector(elements);
233 |
234 | expect(cssSelector).toBe("div.div1, span");
235 | });
236 | });
237 |
238 | describe("Smart table selectors", function () {
239 |
240 | beforeEach(function () {
241 |
242 | $el = jQuery("#tests").html("");
243 | selector = new CssSelector({
244 | parent: jQuery('#tests')[0],
245 | enableSmartTableSelector: true,
246 | query: jQuery
247 | });
248 | });
249 |
250 | it("should be select cells based on text in desciption cell", function () {
251 |
252 | $el.append('
Item:
needed data
Not needed item
Not needed item
');
253 | var element = document.getElementsByClassName('table-cell-test1')[0];
254 | var css_selector = selector.getCssSelector([element]);
255 |
256 | expect(css_selector).toBe("tr:contains('Item:') td.table-cell-test1");
257 | });
258 |
259 | it("should be select cells based on text in desciption cell(th)", function () {
260 |
261 | $el.append('
Item2:
needed data
Not needed item
Not needed item
');
262 | var element = document.getElementsByClassName('table-cell-test3')[0];
263 | var css_selector = selector.getCssSelector([element]);
264 |
265 | expect(css_selector).toBe("tr:contains('Item2:') td.table-cell-test3");
266 | });
267 |
268 | it("should drop its smartness when selecting multiple items", function () {
269 |
270 | $el.append('