parent
c8d83dba7d
commit
62a95823e0
|
@ -17,7 +17,7 @@ var _SELECTOR_REGEXP = RegExpWrapper.create(
|
||||||
'([-\\w]+)|' + // "tag"
|
'([-\\w]+)|' + // "tag"
|
||||||
'(?:\\.([-\\w]+))|' + // ".class"
|
'(?:\\.([-\\w]+))|' + // ".class"
|
||||||
'(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]" or "[name*=value]"
|
'(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]" or "[name*=value]"
|
||||||
'(?:\\))|' + // ")"
|
'(\\))|' + // ")"
|
||||||
'(\\s*,\\s*)'); // ","
|
'(\\s*,\\s*)'); // ","
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -29,11 +29,11 @@ export class CssSelector {
|
||||||
element: string;
|
element: string;
|
||||||
classNames: List<string>;
|
classNames: List<string>;
|
||||||
attrs: List<string>;
|
attrs: List<string>;
|
||||||
notSelector: CssSelector;
|
notSelectors: List<CssSelector>;
|
||||||
static parse(selector: string): List<CssSelector> {
|
static parse(selector: string): List<CssSelector> {
|
||||||
var results = ListWrapper.create();
|
var results = ListWrapper.create();
|
||||||
var _addResult = (res, cssSel) => {
|
var _addResult = (res, cssSel) => {
|
||||||
if (isPresent(cssSel.notSelector) && isBlank(cssSel.element) &&
|
if (cssSel.notSelectors.length > 0 && isBlank(cssSel.element) &&
|
||||||
ListWrapper.isEmpty(cssSel.classNames) && ListWrapper.isEmpty(cssSel.attrs)) {
|
ListWrapper.isEmpty(cssSel.classNames) && ListWrapper.isEmpty(cssSel.attrs)) {
|
||||||
cssSel.element = "*";
|
cssSel.element = "*";
|
||||||
}
|
}
|
||||||
|
@ -43,13 +43,15 @@ export class CssSelector {
|
||||||
var matcher = RegExpWrapper.matcher(_SELECTOR_REGEXP, selector);
|
var matcher = RegExpWrapper.matcher(_SELECTOR_REGEXP, selector);
|
||||||
var match;
|
var match;
|
||||||
var current = cssSelector;
|
var current = cssSelector;
|
||||||
|
var inNot = false;
|
||||||
while (isPresent(match = RegExpMatcherWrapper.next(matcher))) {
|
while (isPresent(match = RegExpMatcherWrapper.next(matcher))) {
|
||||||
if (isPresent(match[1])) {
|
if (isPresent(match[1])) {
|
||||||
if (isPresent(cssSelector.notSelector)) {
|
if (inNot) {
|
||||||
throw new BaseException('Nesting :not is not allowed in a selector');
|
throw new BaseException('Nesting :not is not allowed in a selector');
|
||||||
}
|
}
|
||||||
current.notSelector = new CssSelector();
|
inNot = true;
|
||||||
current = current.notSelector;
|
current = new CssSelector();
|
||||||
|
ListWrapper.push(cssSelector.notSelectors, current);
|
||||||
}
|
}
|
||||||
if (isPresent(match[2])) {
|
if (isPresent(match[2])) {
|
||||||
current.setElement(match[2]);
|
current.setElement(match[2]);
|
||||||
|
@ -61,6 +63,13 @@ export class CssSelector {
|
||||||
current.addAttribute(match[4], match[5]);
|
current.addAttribute(match[4], match[5]);
|
||||||
}
|
}
|
||||||
if (isPresent(match[6])) {
|
if (isPresent(match[6])) {
|
||||||
|
inNot = false;
|
||||||
|
current = cssSelector;
|
||||||
|
}
|
||||||
|
if (isPresent(match[7])) {
|
||||||
|
if (inNot) {
|
||||||
|
throw new BaseException('Multiple selectors in :not are not supported');
|
||||||
|
}
|
||||||
_addResult(results, cssSelector);
|
_addResult(results, cssSelector);
|
||||||
cssSelector = current = new CssSelector();
|
cssSelector = current = new CssSelector();
|
||||||
}
|
}
|
||||||
|
@ -73,12 +82,12 @@ export class CssSelector {
|
||||||
this.element = null;
|
this.element = null;
|
||||||
this.classNames = ListWrapper.create();
|
this.classNames = ListWrapper.create();
|
||||||
this.attrs = ListWrapper.create();
|
this.attrs = ListWrapper.create();
|
||||||
this.notSelector = null;
|
this.notSelectors = ListWrapper.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
isElementSelector(): boolean {
|
isElementSelector(): boolean {
|
||||||
return isPresent(this.element) && ListWrapper.isEmpty(this.classNames) &&
|
return isPresent(this.element) && ListWrapper.isEmpty(this.classNames) &&
|
||||||
ListWrapper.isEmpty(this.attrs) && isBlank(this.notSelector);
|
ListWrapper.isEmpty(this.attrs) && this.notSelectors.length === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
setElement(element: string = null) {
|
setElement(element: string = null) {
|
||||||
|
@ -121,9 +130,8 @@ export class CssSelector {
|
||||||
res += ']';
|
res += ']';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isPresent(this.notSelector)) {
|
ListWrapper.forEach(this.notSelectors,
|
||||||
res += ":not(" + this.notSelector.toString() + ")";
|
(notSelector) => { res += ":not(" + notSelector.toString() + ")"; });
|
||||||
}
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,9 +141,9 @@ export class CssSelector {
|
||||||
* are contained in a given CssSelector.
|
* are contained in a given CssSelector.
|
||||||
*/
|
*/
|
||||||
export class SelectorMatcher {
|
export class SelectorMatcher {
|
||||||
static createNotMatcher(notSelector: CssSelector) {
|
static createNotMatcher(notSelectors: List<CssSelector>) {
|
||||||
var notMatcher = new SelectorMatcher();
|
var notMatcher = new SelectorMatcher();
|
||||||
notMatcher._addSelectable(notSelector, null, null);
|
notMatcher.addSelectables(notSelectors, null);
|
||||||
return notMatcher;
|
return notMatcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -357,22 +365,22 @@ class SelectorListContext {
|
||||||
// Store context to pass back selector and context when a selector is matched
|
// Store context to pass back selector and context when a selector is matched
|
||||||
class SelectorContext {
|
class SelectorContext {
|
||||||
selector: CssSelector;
|
selector: CssSelector;
|
||||||
notSelector: CssSelector;
|
notSelectors: List<CssSelector>;
|
||||||
cbContext; // callback context
|
cbContext; // callback context
|
||||||
listContext: SelectorListContext;
|
listContext: SelectorListContext;
|
||||||
|
|
||||||
constructor(selector: CssSelector, cbContext: any, listContext: SelectorListContext) {
|
constructor(selector: CssSelector, cbContext: any, listContext: SelectorListContext) {
|
||||||
this.selector = selector;
|
this.selector = selector;
|
||||||
this.notSelector = selector.notSelector;
|
this.notSelectors = selector.notSelectors;
|
||||||
this.cbContext = cbContext;
|
this.cbContext = cbContext;
|
||||||
this.listContext = listContext;
|
this.listContext = listContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
finalize(cssSelector: CssSelector, callback /*: (CssSelector, any) => void*/) {
|
finalize(cssSelector: CssSelector, callback /*: (CssSelector, any) => void*/) {
|
||||||
var result = true;
|
var result = true;
|
||||||
if (isPresent(this.notSelector) &&
|
if (this.notSelectors.length > 0 &&
|
||||||
(isBlank(this.listContext) || !this.listContext.alreadyMatched)) {
|
(isBlank(this.listContext) || !this.listContext.alreadyMatched)) {
|
||||||
var notMatcher = SelectorMatcher.createNotMatcher(this.notSelector);
|
var notMatcher = SelectorMatcher.createNotMatcher(this.notSelectors);
|
||||||
result = !notMatcher.match(cssSelector, null);
|
result = !notMatcher.match(cssSelector, null);
|
||||||
}
|
}
|
||||||
if (result && isPresent(callback) &&
|
if (result && isPresent(callback) &&
|
||||||
|
|
|
@ -184,6 +184,13 @@ export function main() {
|
||||||
expect(matched).toEqual([s1[0], 1, s2[0], 2, s3[0], 3, s4[0], 4]);
|
expect(matched).toEqual([s1[0], 1, s2[0], 2, s3[0], 3, s4[0], 4]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should match with multiple :not selectors', () => {
|
||||||
|
matcher.addSelectables(s1 = CssSelector.parse('div:not([a]):not([b])'), 1);
|
||||||
|
expect(matcher.match(CssSelector.parse('div[a]')[0], selectableCollector)).toBe(false);
|
||||||
|
expect(matcher.match(CssSelector.parse('div[b]')[0], selectableCollector)).toBe(false);
|
||||||
|
expect(matcher.match(CssSelector.parse('div[c]')[0], selectableCollector)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it('should select with one match in a list', () => {
|
it('should select with one match in a list', () => {
|
||||||
matcher.addSelectables(s1 = CssSelector.parse('input[type=text], textbox'), 1);
|
matcher.addSelectables(s1 = CssSelector.parse('input[type=text], textbox'), 1);
|
||||||
|
|
||||||
|
@ -256,7 +263,7 @@ export function main() {
|
||||||
expect(cssSelector.attrs.length).toEqual(0);
|
expect(cssSelector.attrs.length).toEqual(0);
|
||||||
expect(cssSelector.classNames.length).toEqual(0);
|
expect(cssSelector.classNames.length).toEqual(0);
|
||||||
|
|
||||||
var notSelector = cssSelector.notSelector;
|
var notSelector = cssSelector.notSelectors[0];
|
||||||
expect(notSelector.element).toEqual(null);
|
expect(notSelector.element).toEqual(null);
|
||||||
expect(notSelector.attrs).toEqual(['attrname', 'attrvalue']);
|
expect(notSelector.attrs).toEqual(['attrname', 'attrvalue']);
|
||||||
expect(notSelector.classNames).toEqual(['someclass']);
|
expect(notSelector.classNames).toEqual(['someclass']);
|
||||||
|
@ -268,7 +275,7 @@ export function main() {
|
||||||
var cssSelector = CssSelector.parse(':not([attrname=attrvalue].someclass)')[0];
|
var cssSelector = CssSelector.parse(':not([attrname=attrvalue].someclass)')[0];
|
||||||
expect(cssSelector.element).toEqual("*");
|
expect(cssSelector.element).toEqual("*");
|
||||||
|
|
||||||
var notSelector = cssSelector.notSelector;
|
var notSelector = cssSelector.notSelectors[0];
|
||||||
expect(notSelector.attrs).toEqual(['attrname', 'attrvalue']);
|
expect(notSelector.attrs).toEqual(['attrname', 'attrvalue']);
|
||||||
expect(notSelector.classNames).toEqual(['someclass']);
|
expect(notSelector.classNames).toEqual(['someclass']);
|
||||||
|
|
||||||
|
@ -280,6 +287,11 @@ export function main() {
|
||||||
.toThrowError('Nesting :not is not allowed in a selector');
|
.toThrowError('Nesting :not is not allowed in a selector');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should throw when multiple selectors in :not', () => {
|
||||||
|
expect(() => { CssSelector.parse('sometag:not(a,b)'); })
|
||||||
|
.toThrowError('Multiple selectors in :not are not supported');
|
||||||
|
});
|
||||||
|
|
||||||
it('should detect lists of selectors', () => {
|
it('should detect lists of selectors', () => {
|
||||||
var cssSelectors = CssSelector.parse('.someclass,[attrname=attrvalue], sometag');
|
var cssSelectors = CssSelector.parse('.someclass,[attrname=attrvalue], sometag');
|
||||||
expect(cssSelectors.length).toEqual(3);
|
expect(cssSelectors.length).toEqual(3);
|
||||||
|
@ -298,10 +310,10 @@ export function main() {
|
||||||
expect(cssSelectors[0].attrs).toEqual(['type', 'text']);
|
expect(cssSelectors[0].attrs).toEqual(['type', 'text']);
|
||||||
|
|
||||||
expect(cssSelectors[1].element).toEqual('*');
|
expect(cssSelectors[1].element).toEqual('*');
|
||||||
expect(cssSelectors[1].notSelector.element).toEqual('textarea');
|
expect(cssSelectors[1].notSelectors[0].element).toEqual('textarea');
|
||||||
|
|
||||||
expect(cssSelectors[2].element).toEqual('textbox');
|
expect(cssSelectors[2].element).toEqual('textbox');
|
||||||
expect(cssSelectors[2].notSelector.classNames).toEqual(['special']);
|
expect(cssSelectors[2].notSelectors[0].classNames).toEqual(['special']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue