refactor(CssSelector): misc cleanup
This commit is contained in:
parent
38c5304b7f
commit
1c012a035f
|
@ -6,13 +6,8 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
import {ListWrapper} from './facade/collection';
|
|
||||||
import {StringWrapper, isBlank, isPresent} from './facade/lang';
|
|
||||||
import {getHtmlTagDefinition} from './ml_parser/html_tags';
|
import {getHtmlTagDefinition} from './ml_parser/html_tags';
|
||||||
|
|
||||||
const _EMPTY_ATTR_VALUE = '';
|
|
||||||
|
|
||||||
const _SELECTOR_REGEXP = new RegExp(
|
const _SELECTOR_REGEXP = new RegExp(
|
||||||
'(\\:not\\()|' + //":not("
|
'(\\:not\\()|' + //":not("
|
||||||
'([-\\w]+)|' + // "tag"
|
'([-\\w]+)|' + // "tag"
|
||||||
|
@ -34,21 +29,21 @@ export class CssSelector {
|
||||||
notSelectors: CssSelector[] = [];
|
notSelectors: CssSelector[] = [];
|
||||||
|
|
||||||
static parse(selector: string): CssSelector[] {
|
static parse(selector: string): CssSelector[] {
|
||||||
var results: CssSelector[] = [];
|
const results: CssSelector[] = [];
|
||||||
var _addResult = (res: CssSelector[], cssSel: CssSelector) => {
|
const _addResult = (res: CssSelector[], cssSel: CssSelector) => {
|
||||||
if (cssSel.notSelectors.length > 0 && isBlank(cssSel.element) &&
|
if (cssSel.notSelectors.length > 0 && !cssSel.element && cssSel.classNames.length == 0 &&
|
||||||
ListWrapper.isEmpty(cssSel.classNames) && ListWrapper.isEmpty(cssSel.attrs)) {
|
cssSel.attrs.length == 0) {
|
||||||
cssSel.element = '*';
|
cssSel.element = '*';
|
||||||
}
|
}
|
||||||
res.push(cssSel);
|
res.push(cssSel);
|
||||||
};
|
};
|
||||||
var cssSelector = new CssSelector();
|
let cssSelector = new CssSelector();
|
||||||
var match: string[];
|
let match: string[];
|
||||||
var current = cssSelector;
|
let current = cssSelector;
|
||||||
var inNot = false;
|
let inNot = false;
|
||||||
_SELECTOR_REGEXP.lastIndex = 0;
|
_SELECTOR_REGEXP.lastIndex = 0;
|
||||||
while (isPresent(match = _SELECTOR_REGEXP.exec(selector))) {
|
while (match = _SELECTOR_REGEXP.exec(selector)) {
|
||||||
if (isPresent(match[1])) {
|
if (match[1]) {
|
||||||
if (inNot) {
|
if (inNot) {
|
||||||
throw new Error('Nesting :not is not allowed in a selector');
|
throw new Error('Nesting :not is not allowed in a selector');
|
||||||
}
|
}
|
||||||
|
@ -56,20 +51,20 @@ export class CssSelector {
|
||||||
current = new CssSelector();
|
current = new CssSelector();
|
||||||
cssSelector.notSelectors.push(current);
|
cssSelector.notSelectors.push(current);
|
||||||
}
|
}
|
||||||
if (isPresent(match[2])) {
|
if (match[2]) {
|
||||||
current.setElement(match[2]);
|
current.setElement(match[2]);
|
||||||
}
|
}
|
||||||
if (isPresent(match[3])) {
|
if (match[3]) {
|
||||||
current.addClassName(match[3]);
|
current.addClassName(match[3]);
|
||||||
}
|
}
|
||||||
if (isPresent(match[4])) {
|
if (match[4]) {
|
||||||
current.addAttribute(match[4], match[5]);
|
current.addAttribute(match[4], match[5]);
|
||||||
}
|
}
|
||||||
if (isPresent(match[6])) {
|
if (match[6]) {
|
||||||
inNot = false;
|
inNot = false;
|
||||||
current = cssSelector;
|
current = cssSelector;
|
||||||
}
|
}
|
||||||
if (isPresent(match[7])) {
|
if (match[7]) {
|
||||||
if (inNot) {
|
if (inNot) {
|
||||||
throw new Error('Multiple selectors in :not are not supported');
|
throw new Error('Multiple selectors in :not are not supported');
|
||||||
}
|
}
|
||||||
|
@ -106,37 +101,22 @@ export class CssSelector {
|
||||||
`<${tagName}${classAttr}${attrs}></${tagName}>`;
|
`<${tagName}${classAttr}${attrs}></${tagName}>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
addAttribute(name: string, value: string = _EMPTY_ATTR_VALUE) {
|
addAttribute(name: string, value: string = '') {
|
||||||
this.attrs.push(name);
|
this.attrs.push(name, value && value.toLowerCase() || '');
|
||||||
if (isPresent(value)) {
|
|
||||||
value = value.toLowerCase();
|
|
||||||
} else {
|
|
||||||
value = _EMPTY_ATTR_VALUE;
|
|
||||||
}
|
|
||||||
this.attrs.push(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addClassName(name: string) { this.classNames.push(name.toLowerCase()); }
|
addClassName(name: string) { this.classNames.push(name.toLowerCase()); }
|
||||||
|
|
||||||
toString(): string {
|
toString(): string {
|
||||||
var res = '';
|
let res: string = this.element || '';
|
||||||
if (isPresent(this.element)) {
|
if (this.classNames) {
|
||||||
res += this.element;
|
this.classNames.forEach(klass => res += `.${klass}`);
|
||||||
}
|
}
|
||||||
if (isPresent(this.classNames)) {
|
if (this.attrs) {
|
||||||
for (var i = 0; i < this.classNames.length; i++) {
|
for (let i = 0; i < this.attrs.length; i += 2) {
|
||||||
res += '.' + this.classNames[i];
|
const name = this.attrs[i];
|
||||||
}
|
const value = this.attrs[i + 1];
|
||||||
}
|
res += `[${name}${value ? '=' + value : ''}]`;
|
||||||
if (isPresent(this.attrs)) {
|
|
||||||
for (var i = 0; i < this.attrs.length;) {
|
|
||||||
var attrName = this.attrs[i++];
|
|
||||||
var attrValue = this.attrs[i++];
|
|
||||||
res += '[' + attrName;
|
|
||||||
if (attrValue.length > 0) {
|
|
||||||
res += '=' + attrValue;
|
|
||||||
}
|
|
||||||
res += ']';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.notSelectors.forEach(notSelector => res += `:not(${notSelector})`);
|
this.notSelectors.forEach(notSelector => res += `:not(${notSelector})`);
|
||||||
|
@ -150,26 +130,26 @@ export class CssSelector {
|
||||||
*/
|
*/
|
||||||
export class SelectorMatcher {
|
export class SelectorMatcher {
|
||||||
static createNotMatcher(notSelectors: CssSelector[]): SelectorMatcher {
|
static createNotMatcher(notSelectors: CssSelector[]): SelectorMatcher {
|
||||||
var notMatcher = new SelectorMatcher();
|
const notMatcher = new SelectorMatcher();
|
||||||
notMatcher.addSelectables(notSelectors, null);
|
notMatcher.addSelectables(notSelectors, null);
|
||||||
return notMatcher;
|
return notMatcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _elementMap = new Map<string, SelectorContext[]>();
|
private _elementMap: {[k: string]: SelectorContext[]} = {};
|
||||||
private _elementPartialMap = new Map<string, SelectorMatcher>();
|
private _elementPartialMap: {[k: string]: SelectorMatcher} = {};
|
||||||
private _classMap = new Map<string, SelectorContext[]>();
|
private _classMap: {[k: string]: SelectorContext[]} = {};
|
||||||
private _classPartialMap = new Map<string, SelectorMatcher>();
|
private _classPartialMap: {[k: string]: SelectorMatcher} = {};
|
||||||
private _attrValueMap = new Map<string, Map<string, SelectorContext[]>>();
|
private _attrValueMap: {[k: string]: {[k: string]: SelectorContext[]}} = {};
|
||||||
private _attrValuePartialMap = new Map<string, Map<string, SelectorMatcher>>();
|
private _attrValuePartialMap: {[k: string]: {[k: string]: SelectorMatcher}} = {};
|
||||||
private _listContexts: SelectorListContext[] = [];
|
private _listContexts: SelectorListContext[] = [];
|
||||||
|
|
||||||
addSelectables(cssSelectors: CssSelector[], callbackCtxt?: any) {
|
addSelectables(cssSelectors: CssSelector[], callbackCtxt?: any) {
|
||||||
var listContext: SelectorListContext = null;
|
let listContext: SelectorListContext = null;
|
||||||
if (cssSelectors.length > 1) {
|
if (cssSelectors.length > 1) {
|
||||||
listContext = new SelectorListContext(cssSelectors);
|
listContext = new SelectorListContext(cssSelectors);
|
||||||
this._listContexts.push(listContext);
|
this._listContexts.push(listContext);
|
||||||
}
|
}
|
||||||
for (var i = 0; i < cssSelectors.length; i++) {
|
for (let i = 0; i < cssSelectors.length; i++) {
|
||||||
this._addSelectable(cssSelectors[i], callbackCtxt, listContext);
|
this._addSelectable(cssSelectors[i], callbackCtxt, listContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -181,14 +161,14 @@ export class SelectorMatcher {
|
||||||
*/
|
*/
|
||||||
private _addSelectable(
|
private _addSelectable(
|
||||||
cssSelector: CssSelector, callbackCtxt: any, listContext: SelectorListContext) {
|
cssSelector: CssSelector, callbackCtxt: any, listContext: SelectorListContext) {
|
||||||
var matcher: SelectorMatcher = this;
|
let matcher: SelectorMatcher = this;
|
||||||
var element = cssSelector.element;
|
const element = cssSelector.element;
|
||||||
var classNames = cssSelector.classNames;
|
const classNames = cssSelector.classNames;
|
||||||
var attrs = cssSelector.attrs;
|
const attrs = cssSelector.attrs;
|
||||||
var selectable = new SelectorContext(cssSelector, callbackCtxt, listContext);
|
const selectable = new SelectorContext(cssSelector, callbackCtxt, listContext);
|
||||||
|
|
||||||
if (isPresent(element)) {
|
if (element) {
|
||||||
var isTerminal = attrs.length === 0 && classNames.length === 0;
|
const isTerminal = attrs.length === 0 && classNames.length === 0;
|
||||||
if (isTerminal) {
|
if (isTerminal) {
|
||||||
this._addTerminal(matcher._elementMap, element, selectable);
|
this._addTerminal(matcher._elementMap, element, selectable);
|
||||||
} else {
|
} else {
|
||||||
|
@ -196,10 +176,10 @@ export class SelectorMatcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPresent(classNames)) {
|
if (classNames) {
|
||||||
for (var index = 0; index < classNames.length; index++) {
|
for (let i = 0; i < classNames.length; i++) {
|
||||||
var isTerminal = attrs.length === 0 && index === classNames.length - 1;
|
const isTerminal = attrs.length === 0 && i === classNames.length - 1;
|
||||||
var className = classNames[index];
|
const className = classNames[i];
|
||||||
if (isTerminal) {
|
if (isTerminal) {
|
||||||
this._addTerminal(matcher._classMap, className, selectable);
|
this._addTerminal(matcher._classMap, className, selectable);
|
||||||
} else {
|
} else {
|
||||||
|
@ -208,47 +188,47 @@ export class SelectorMatcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPresent(attrs)) {
|
if (attrs) {
|
||||||
for (var index = 0; index < attrs.length;) {
|
for (let i = 0; i < attrs.length; i += 2) {
|
||||||
var isTerminal = index === attrs.length - 2;
|
const isTerminal = i === attrs.length - 2;
|
||||||
var attrName = attrs[index++];
|
const name = attrs[i];
|
||||||
var attrValue = attrs[index++];
|
const value = attrs[i + 1];
|
||||||
if (isTerminal) {
|
if (isTerminal) {
|
||||||
var terminalMap = matcher._attrValueMap;
|
const terminalMap = matcher._attrValueMap;
|
||||||
var terminalValuesMap = terminalMap.get(attrName);
|
let terminalValuesMap = terminalMap[name];
|
||||||
if (!terminalValuesMap) {
|
if (!terminalValuesMap) {
|
||||||
terminalValuesMap = new Map<string, SelectorContext[]>();
|
terminalValuesMap = {};
|
||||||
terminalMap.set(attrName, terminalValuesMap);
|
terminalMap[name] = terminalValuesMap;
|
||||||
}
|
}
|
||||||
this._addTerminal(terminalValuesMap, attrValue, selectable);
|
this._addTerminal(terminalValuesMap, value, selectable);
|
||||||
} else {
|
} else {
|
||||||
var parttialMap = matcher._attrValuePartialMap;
|
let partialMap = matcher._attrValuePartialMap;
|
||||||
var partialValuesMap = parttialMap.get(attrName);
|
let partialValuesMap = partialMap[name];
|
||||||
if (!partialValuesMap) {
|
if (!partialValuesMap) {
|
||||||
partialValuesMap = new Map<string, SelectorMatcher>();
|
partialValuesMap = {};
|
||||||
parttialMap.set(attrName, partialValuesMap);
|
partialMap[name] = partialValuesMap;
|
||||||
}
|
}
|
||||||
matcher = this._addPartial(partialValuesMap, attrValue);
|
matcher = this._addPartial(partialValuesMap, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _addTerminal(
|
private _addTerminal(
|
||||||
map: Map<string, SelectorContext[]>, name: string, selectable: SelectorContext) {
|
map: {[k: string]: SelectorContext[]}, name: string, selectable: SelectorContext) {
|
||||||
var terminalList = map.get(name);
|
let terminalList = map[name];
|
||||||
if (!terminalList) {
|
if (!terminalList) {
|
||||||
terminalList = [];
|
terminalList = [];
|
||||||
map.set(name, terminalList);
|
map[name] = terminalList;
|
||||||
}
|
}
|
||||||
terminalList.push(selectable);
|
terminalList.push(selectable);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _addPartial(map: Map<string, SelectorMatcher>, name: string): SelectorMatcher {
|
private _addPartial(map: {[k: string]: SelectorMatcher}, name: string): SelectorMatcher {
|
||||||
var matcher = map.get(name);
|
let matcher = map[name];
|
||||||
if (!matcher) {
|
if (!matcher) {
|
||||||
matcher = new SelectorMatcher();
|
matcher = new SelectorMatcher();
|
||||||
map.set(name, matcher);
|
map[name] = matcher;
|
||||||
}
|
}
|
||||||
return matcher;
|
return matcher;
|
||||||
}
|
}
|
||||||
|
@ -261,12 +241,12 @@ export class SelectorMatcher {
|
||||||
* @return boolean true if a match was found
|
* @return boolean true if a match was found
|
||||||
*/
|
*/
|
||||||
match(cssSelector: CssSelector, matchedCallback: (c: CssSelector, a: any) => void): boolean {
|
match(cssSelector: CssSelector, matchedCallback: (c: CssSelector, a: any) => void): boolean {
|
||||||
var result = false;
|
let result = false;
|
||||||
var element = cssSelector.element;
|
const element = cssSelector.element;
|
||||||
var classNames = cssSelector.classNames;
|
const classNames = cssSelector.classNames;
|
||||||
var attrs = cssSelector.attrs;
|
const attrs = cssSelector.attrs;
|
||||||
|
|
||||||
for (var i = 0; i < this._listContexts.length; i++) {
|
for (let i = 0; i < this._listContexts.length; i++) {
|
||||||
this._listContexts[i].alreadyMatched = false;
|
this._listContexts[i].alreadyMatched = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,9 +254,9 @@ export class SelectorMatcher {
|
||||||
result = this._matchPartial(this._elementPartialMap, element, cssSelector, matchedCallback) ||
|
result = this._matchPartial(this._elementPartialMap, element, cssSelector, matchedCallback) ||
|
||||||
result;
|
result;
|
||||||
|
|
||||||
if (isPresent(classNames)) {
|
if (classNames) {
|
||||||
for (var index = 0; index < classNames.length; index++) {
|
for (let i = 0; i < classNames.length; i++) {
|
||||||
var className = classNames[index];
|
const className = classNames[i];
|
||||||
result =
|
result =
|
||||||
this._matchTerminal(this._classMap, className, cssSelector, matchedCallback) || result;
|
this._matchTerminal(this._classMap, className, cssSelector, matchedCallback) || result;
|
||||||
result =
|
result =
|
||||||
|
@ -285,28 +265,25 @@ export class SelectorMatcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPresent(attrs)) {
|
if (attrs) {
|
||||||
for (var index = 0; index < attrs.length;) {
|
for (let i = 0; i < attrs.length; i += 2) {
|
||||||
var attrName = attrs[index++];
|
const name = attrs[i];
|
||||||
var attrValue = attrs[index++];
|
const value = attrs[i + 1];
|
||||||
|
|
||||||
var terminalValuesMap = this._attrValueMap.get(attrName);
|
const terminalValuesMap = this._attrValueMap[name];
|
||||||
if (!StringWrapper.equals(attrValue, _EMPTY_ATTR_VALUE)) {
|
if (value) {
|
||||||
result = this._matchTerminal(
|
result =
|
||||||
terminalValuesMap, _EMPTY_ATTR_VALUE, cssSelector, matchedCallback) ||
|
this._matchTerminal(terminalValuesMap, '', cssSelector, matchedCallback) || result;
|
||||||
result;
|
|
||||||
}
|
|
||||||
result = this._matchTerminal(terminalValuesMap, attrValue, cssSelector, matchedCallback) ||
|
|
||||||
result;
|
|
||||||
|
|
||||||
var partialValuesMap = this._attrValuePartialMap.get(attrName);
|
|
||||||
if (!StringWrapper.equals(attrValue, _EMPTY_ATTR_VALUE)) {
|
|
||||||
result = this._matchPartial(
|
|
||||||
partialValuesMap, _EMPTY_ATTR_VALUE, cssSelector, matchedCallback) ||
|
|
||||||
result;
|
|
||||||
}
|
}
|
||||||
result =
|
result =
|
||||||
this._matchPartial(partialValuesMap, attrValue, cssSelector, matchedCallback) || result;
|
this._matchTerminal(terminalValuesMap, value, cssSelector, matchedCallback) || result;
|
||||||
|
|
||||||
|
const partialValuesMap = this._attrValuePartialMap[name];
|
||||||
|
if (value) {
|
||||||
|
result = this._matchPartial(partialValuesMap, '', cssSelector, matchedCallback) || result;
|
||||||
|
}
|
||||||
|
result =
|
||||||
|
this._matchPartial(partialValuesMap, value, cssSelector, matchedCallback) || result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -314,24 +291,24 @@ export class SelectorMatcher {
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_matchTerminal(
|
_matchTerminal(
|
||||||
map: Map<string, SelectorContext[]>, name: string, cssSelector: CssSelector,
|
map: {[k: string]: SelectorContext[]}, name: string, cssSelector: CssSelector,
|
||||||
matchedCallback: (c: CssSelector, a: any) => void): boolean {
|
matchedCallback: (c: CssSelector, a: any) => void): boolean {
|
||||||
if (!map || isBlank(name)) {
|
if (!map || typeof name !== 'string') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var selectables = map.get(name);
|
let selectables = map[name];
|
||||||
var starSelectables = map.get('*');
|
const starSelectables = map['*'];
|
||||||
if (isPresent(starSelectables)) {
|
if (starSelectables) {
|
||||||
selectables = selectables.concat(starSelectables);
|
selectables = selectables.concat(starSelectables);
|
||||||
}
|
}
|
||||||
if (!selectables) {
|
if (!selectables) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var selectable: SelectorContext;
|
let selectable: SelectorContext;
|
||||||
var result = false;
|
let result = false;
|
||||||
for (var index = 0; index < selectables.length; index++) {
|
for (let i = 0; i < selectables.length; i++) {
|
||||||
selectable = selectables[index];
|
selectable = selectables[i];
|
||||||
result = selectable.finalize(cssSelector, matchedCallback) || result;
|
result = selectable.finalize(cssSelector, matchedCallback) || result;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -339,12 +316,13 @@ export class SelectorMatcher {
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_matchPartial(
|
_matchPartial(
|
||||||
map: Map<string, SelectorMatcher>, name: string, cssSelector: CssSelector,
|
map: {[k: string]: SelectorMatcher}, name: string, cssSelector: CssSelector,
|
||||||
matchedCallback: (c: CssSelector, a: any) => void): boolean {
|
matchedCallback: (c: CssSelector, a: any) => void): boolean {
|
||||||
if (!map || isBlank(name)) {
|
if (!map || typeof name !== 'string') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
var nestedSelector = map.get(name);
|
|
||||||
|
const nestedSelector = map[name];
|
||||||
if (!nestedSelector) {
|
if (!nestedSelector) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -373,13 +351,13 @@ export class SelectorContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
finalize(cssSelector: CssSelector, callback: (c: CssSelector, a: any) => void): boolean {
|
finalize(cssSelector: CssSelector, callback: (c: CssSelector, a: any) => void): boolean {
|
||||||
var result = true;
|
let result = true;
|
||||||
if (this.notSelectors.length > 0 && (!this.listContext || !this.listContext.alreadyMatched)) {
|
if (this.notSelectors.length > 0 && (!this.listContext || !this.listContext.alreadyMatched)) {
|
||||||
var notMatcher = SelectorMatcher.createNotMatcher(this.notSelectors);
|
const notMatcher = SelectorMatcher.createNotMatcher(this.notSelectors);
|
||||||
result = !notMatcher.match(cssSelector, null);
|
result = !notMatcher.match(cssSelector, null);
|
||||||
}
|
}
|
||||||
if (result && isPresent(callback) && (!this.listContext || !this.listContext.alreadyMatched)) {
|
if (result && callback && (!this.listContext || !this.listContext.alreadyMatched)) {
|
||||||
if (isPresent(this.listContext)) {
|
if (this.listContext) {
|
||||||
this.listContext.alreadyMatched = true;
|
this.listContext.alreadyMatched = true;
|
||||||
}
|
}
|
||||||
callback(this.selector, this.cbContext);
|
callback(this.selector, this.cbContext);
|
||||||
|
|
Loading…
Reference in New Issue