Index: third_party/pkg/angular/lib/core_dom/selector.dart |
diff --git a/third_party/pkg/angular/lib/core_dom/selector.dart b/third_party/pkg/angular/lib/core_dom/selector.dart |
index 2492d0608208a7831af9a8dc56412db804a71fe3..fec6946e86c683a223c3acbae18ceed9489b51fa 100644 |
--- a/third_party/pkg/angular/lib/core_dom/selector.dart |
+++ b/third_party/pkg/angular/lib/core_dom/selector.dart |
@@ -1,4 +1,4 @@ |
-part of angular.core.dom; |
+part of angular.core.dom_internal; |
/** |
* DirectiveSelector is a function which given a node it will return a |
@@ -7,8 +7,7 @@ part of angular.core.dom; |
* DirectiveSelector is used by the [Compiler] during the template walking |
* to extract the [DirectiveRef]s. |
* |
- * DirectiveSelector can be created using the [directiveSelectorFactory] |
- * method. |
+ * DirectiveSelector can be created using the [DirectiveSelectorFactory]. |
* |
* The DirectiveSelector supports CSS selectors which do not cross |
* element boundaries only. The selectors can have any mix of element-name, |
@@ -16,39 +15,171 @@ part of angular.core.dom; |
* |
* Examples: |
* |
- * <pre> |
- * element |
- * .class |
- * [attribute] |
- * [attribute=value] |
- * element[attribute1][attribute2=value] |
- * </pre> |
- * |
- * |
+ * * element |
+ * * .class |
+ * * [attribute] |
+ * * [attribute=value] |
+ * * element[attribute1][attribute2=value] |
+ * * :contains(/abc/) |
+ */ |
+class DirectiveSelector { |
+ ElementBinderFactory _binderFactory; |
+ DirectiveMap _directives; |
+ var elementSelector; |
+ var attrSelector; |
+ var textSelector; |
+ |
+ DirectiveSelector(this._directives, this._binderFactory) { |
+ elementSelector = new _ElementSelector(''); |
+ attrSelector = <_ContainsSelector>[]; |
+ textSelector = <_ContainsSelector>[]; |
+ _directives.forEach((Directive annotation, Type type) { |
+ var match; |
+ var selector = annotation.selector; |
+ List<_SelectorPart> selectorParts; |
+ if (selector == null) { |
+ throw new ArgumentError('Missing selector annotation for $type'); |
+ } |
+ |
+ if ((match = _CONTAINS_REGEXP.firstMatch(selector)) != null) { |
+ textSelector.add(new _ContainsSelector(annotation, match.group(1))); |
+ } else if ((match = _ATTR_CONTAINS_REGEXP.firstMatch(selector)) != null) { |
+ attrSelector.add(new _ContainsSelector(annotation, match[1])); |
+ } else if ((selectorParts = _splitCss(selector, type)) != null){ |
+ elementSelector.addDirective(selectorParts, |
+ new _Directive(type, annotation)); |
+ } else { |
+ throw new ArgumentError('Unsupported Selector: $selector'); |
+ } |
+ }); |
+ } |
+ |
+ ElementBinder matchElement(dom.Node node) { |
+ assert(node is dom.Element); |
+ |
+ ElementBinderBuilder builder = _binderFactory.builder(); |
+ List<_ElementSelector> partialSelection; |
+ var classes = <String, bool>{}; |
+ Map<String, String> attrs = {}; |
+ |
+ dom.Element element = node; |
+ String nodeName = element.tagName.toLowerCase(); |
+ |
+ // Set default attribute |
+ if (nodeName == 'input' && !element.attributes.containsKey('type')) { |
+ element.attributes['type'] = 'text'; |
+ } |
+ |
+ // Select node |
+ partialSelection = elementSelector.selectNode(builder, |
+ partialSelection, element, nodeName); |
+ |
+ // Select .name |
+ if ((element.classes) != null) { |
+ for (var name in element.classes) { |
+ classes[name] = true; |
+ partialSelection = elementSelector.selectClass(builder, |
+ partialSelection, element, name); |
+ } |
+ } |
+ |
+ // Select [attributes] |
+ element.attributes.forEach((attrName, value) { |
+ |
+ if (attrName.startsWith("on-")) { |
+ builder.onEvents[attrName] = value; |
+ } else if (attrName.startsWith("bind-")) { |
+ builder.bindAttrs[attrName] = value; |
+ } |
+ |
+ attrs[attrName] = value; |
+ for (var k = 0; k < attrSelector.length; k++) { |
+ _ContainsSelector selectorRegExp = attrSelector[k]; |
+ if (selectorRegExp.regexp.hasMatch(value)) { |
+ // this directive is matched on any attribute name, and so |
+ // we need to pass the name to the directive by prefixing it to |
+ // the value. Yes it is a bit of a hack. |
+ _directives[selectorRegExp.annotation].forEach((type) { |
+ builder.addDirective(new DirectiveRef( |
+ node, type, selectorRegExp.annotation, '$attrName=$value')); |
+ }); |
+ } |
+ } |
+ |
+ partialSelection = elementSelector.selectAttr(builder, |
+ partialSelection, node, attrName, value); |
+ }); |
+ |
+ while (partialSelection != null) { |
+ List<_ElementSelector> elementSelectors = partialSelection; |
+ partialSelection = null; |
+ elementSelectors.forEach((_ElementSelector elementSelector) { |
+ classes.forEach((className, _) { |
+ partialSelection = elementSelector.selectClass(builder, |
+ partialSelection, node, className); |
+ }); |
+ attrs.forEach((attrName, value) { |
+ partialSelection = elementSelector.selectAttr(builder, |
+ partialSelection, node, attrName, value); |
+ }); |
+ }); |
+ } |
+ return builder.binder; |
+ } |
+ |
+ ElementBinder matchText(dom.Node node) { |
+ ElementBinderBuilder builder = _binderFactory.builder(); |
+ |
+ var value = node.nodeValue; |
+ for (var k = 0; k < textSelector.length; k++) { |
+ var selectorRegExp = textSelector[k]; |
+ if (selectorRegExp.regexp.hasMatch(value)) { |
+ _directives[selectorRegExp.annotation].forEach((type) { |
+ builder.addDirective(new DirectiveRef(node, type, |
+ selectorRegExp.annotation, value)); |
+ }); |
+ } |
+ } |
+ return builder.binder; |
+ } |
+ |
+ ElementBinder matchComment(dom.Node node) => |
+ _binderFactory.builder().binder; |
+} |
+ |
+/** |
+ * Factory for creating a [DirectiveSelector]. |
*/ |
-typedef List<DirectiveRef> DirectiveSelector(dom.Node node); |
+@Injectable() |
+class DirectiveSelectorFactory { |
+ ElementBinderFactory _binderFactory; |
+ |
+ DirectiveSelectorFactory(this._binderFactory); |
+ |
+ DirectiveSelector selector(DirectiveMap directives) => |
+ new DirectiveSelector(directives, _binderFactory); |
+} |
class _Directive { |
final Type type; |
- final NgAnnotation annotation; |
+ final Directive annotation; |
_Directive(this.type, this.annotation); |
toString() => annotation.selector; |
} |
- |
class _ContainsSelector { |
- final NgAnnotation annotation; |
+ final Directive annotation; |
final RegExp regexp; |
_ContainsSelector(this.annotation, String regexp) |
: regexp = new RegExp(regexp); |
} |
-var _SELECTOR_REGEXP = new RegExp(r'^(?:([\w\-]+)|(?:\.([\w\-]+))|' |
- r'(?:\[([\w\-\*]+)(?:=([^\]]*))?\]))'); |
-var _COMMENT_COMPONENT_REGEXP = new RegExp(r'^\[([\w\-]+)(?:\=(.*))?\]$'); |
+var _SELECTOR_REGEXP = new RegExp(r'^(?:([-\w]+)|(?:\.([-\w]+))|' |
+ r'(?:\[([-\w*]+)(?:=([^\]]*))?\]))'); |
+var _COMMENT_COMPONENT_REGEXP = new RegExp(r'^\[([-\w]+)(?:\=(.*))?\]$'); |
var _CONTAINS_REGEXP = new RegExp(r'^:contains\(\/(.+)\/\)$'); // |
var _ATTR_CONTAINS_REGEXP = new RegExp(r'^\[\*=\/(.+)\/\]$'); // |
@@ -64,7 +195,6 @@ class _SelectorPart { |
const _SelectorPart.fromClass(this.className) |
: element = null, attrName = null, attrValue = null; |
- |
const _SelectorPart.fromAttribute(this.attrName, this.attrValue) |
: element = null, className = null; |
@@ -76,6 +206,12 @@ class _SelectorPart { |
: element; |
} |
+_addRefs(ElementBinderBuilder builder, List<_Directive> directives, dom.Node node, |
+ [String attrValue]) { |
+ directives.forEach((directive) { |
+ builder.addDirective(new DirectiveRef(node, directive.type, directive.annotation, attrValue)); |
+ }); |
+} |
class _ElementSelector { |
final String name; |
@@ -97,9 +233,7 @@ class _ElementSelector { |
var name; |
if ((name = selectorPart.element) != null) { |
if (terminal) { |
- elementMap |
- .putIfAbsent(name, () => []) |
- .add(directive); |
+ elementMap.putIfAbsent(name, () => []).add(directive); |
} else { |
elementPartialMap |
.putIfAbsent(name, () => new _ElementSelector(name)) |
@@ -133,18 +267,13 @@ class _ElementSelector { |
} |
} |
- _addRefs(List<DirectiveRef> refs, List<_Directive> directives, dom.Node node, |
- [String attrValue]) { |
- directives.forEach((directive) => |
- refs.add(new DirectiveRef(node, directive.type, directive.annotation, |
- attrValue))); |
- } |
- List<_ElementSelector> selectNode(List<DirectiveRef> refs, |
+ |
+ List<_ElementSelector> selectNode(ElementBinderBuilder builder, |
List<_ElementSelector> partialSelection, |
dom.Node node, String nodeName) { |
if (elementMap.containsKey(nodeName)) { |
- _addRefs(refs, elementMap[nodeName], node); |
+ _addRefs(builder, elementMap[nodeName], node); |
} |
if (elementPartialMap.containsKey(nodeName)) { |
if (partialSelection == null) { |
@@ -155,11 +284,11 @@ class _ElementSelector { |
return partialSelection; |
} |
- List<_ElementSelector> selectClass(List<DirectiveRef> refs, |
+ List<_ElementSelector> selectClass(ElementBinderBuilder builder, |
List<_ElementSelector> partialSelection, |
dom.Node node, String className) { |
if (classMap.containsKey(className)) { |
- _addRefs(refs, classMap[className], node); |
+ _addRefs(builder, classMap[className], node); |
} |
if (classPartialMap.containsKey(className)) { |
if (partialSelection == null) { |
@@ -170,7 +299,7 @@ class _ElementSelector { |
return partialSelection; |
} |
- List<_ElementSelector> selectAttr(List<DirectiveRef> refs, |
+ List<_ElementSelector> selectAttr(ElementBinderBuilder builder, |
List<_ElementSelector> partialSelection, |
dom.Node node, String attrName, |
String attrValue) { |
@@ -180,10 +309,10 @@ class _ElementSelector { |
if (matchingKey != null) { |
Map<String, List<_Directive>> valuesMap = attrValueMap[matchingKey]; |
if (valuesMap.containsKey('')) { |
- _addRefs(refs, valuesMap[''], node, attrValue); |
+ _addRefs(builder, valuesMap[''], node, attrValue); |
} |
if (attrValue != '' && valuesMap.containsKey(attrValue)) { |
- _addRefs(refs, valuesMap[attrValue], node, attrValue); |
+ _addRefs(builder, valuesMap[attrValue], node, attrValue); |
} |
} |
if (attrValuePartialMap.containsKey(attrName)) { |
@@ -205,9 +334,14 @@ class _ElementSelector { |
return partialSelection; |
} |
+ // A global cache for the _matchingKey RegExps. The size is bounded by |
+ // the number of attribute directive selectors used in the application. |
+ static var _matchingKeyCache = <String, RegExp>{}; |
+ |
String _matchingKey(Iterable<String> keys, String attrName) => |
keys.firstWhere((key) => |
- new RegExp('^${key.replaceAll('*', r'[\w\-]+')}\$') |
+ _matchingKeyCache.putIfAbsent(key, |
+ () => new RegExp('^${key.replaceAll('*', r'[\w\-]+')}\$')) |
.hasMatch(attrName), orElse: () => null); |
toString() => 'ElementSelector($name)'; |
@@ -237,128 +371,3 @@ List<_SelectorPart> _splitCss(String selector, Type type) { |
} |
return parts; |
} |
- |
-/** |
- * Factory method for creating a [DirectiveSelector]. |
- */ |
-DirectiveSelector directiveSelectorFactory(DirectiveMap directives) { |
- |
- var elementSelector = new _ElementSelector(''); |
- var attrSelector = <_ContainsSelector>[]; |
- var textSelector = <_ContainsSelector>[]; |
- directives.forEach((NgAnnotation annotation, Type type) { |
- var match; |
- var selector = annotation.selector; |
- List<_SelectorPart> selectorParts; |
- if (selector == null) { |
- throw new ArgumentError('Missing selector annotation for $type'); |
- } |
- |
- if ((match = _CONTAINS_REGEXP.firstMatch(selector)) != null) { |
- textSelector.add(new _ContainsSelector(annotation, match.group(1))); |
- } else if ((match = _ATTR_CONTAINS_REGEXP.firstMatch(selector)) != null) { |
- attrSelector.add(new _ContainsSelector(annotation, match[1])); |
- } else if ((selectorParts = _splitCss(selector, type)) != null){ |
- elementSelector.addDirective(selectorParts, |
- new _Directive(type, annotation)); |
- } else { |
- throw new ArgumentError('Unsupported Selector: $selector'); |
- } |
- }); |
- |
- return (dom.Node node) { |
- var directiveRefs = <DirectiveRef>[]; |
- List<_ElementSelector> partialSelection; |
- var classes = <String, bool>{}; |
- var attrs = <String, String>{}; |
- |
- switch(node.nodeType) { |
- case 1: // Element |
- dom.Element element = node; |
- String nodeName = element.tagName.toLowerCase(); |
- Map<String, String> attrs = {}; |
- |
- // Set default attribute |
- if (nodeName == 'input' && !element.attributes.containsKey('type')) { |
- element.attributes['type'] = 'text'; |
- } |
- |
- // Select node |
- partialSelection = elementSelector.selectNode(directiveRefs, |
- partialSelection, element, nodeName); |
- |
- // Select .name |
- if ((element.classes) != null) { |
- for (var name in element.classes) { |
- classes[name] = true; |
- partialSelection = elementSelector.selectClass(directiveRefs, |
- partialSelection, element, name); |
- } |
- } |
- |
- // Select [attributes] |
- element.attributes.forEach((attrName, value) { |
- attrs[attrName] = value; |
- for (var k = 0; k < attrSelector.length; k++) { |
- _ContainsSelector selectorRegExp = attrSelector[k]; |
- if (selectorRegExp.regexp.hasMatch(value)) { |
- // this directive is matched on any attribute name, and so |
- // we need to pass the name to the directive by prefixing it to |
- // the value. Yes it is a bit of a hack. |
- directives[selectorRegExp.annotation].forEach((type) { |
- directiveRefs.add(new DirectiveRef( |
- node, type, selectorRegExp.annotation, '$attrName=$value')); |
- }); |
- } |
- } |
- |
- partialSelection = elementSelector.selectAttr(directiveRefs, |
- partialSelection, node, attrName, value); |
- }); |
- |
- while(partialSelection != null) { |
- List<_ElementSelector> elementSelectors = partialSelection; |
- partialSelection = null; |
- elementSelectors.forEach((_ElementSelector elementSelector) { |
- classes.forEach((className, _) { |
- partialSelection = elementSelector.selectClass(directiveRefs, |
- partialSelection, node, className); |
- }); |
- attrs.forEach((attrName, value) { |
- partialSelection = elementSelector.selectAttr(directiveRefs, |
- partialSelection, node, attrName, value); |
- }); |
- }); |
- } |
- break; |
- case 3: // Text Node |
- var value = node.nodeValue; |
- for (var k = 0; k < textSelector.length; k++) { |
- var selectorRegExp = textSelector[k]; |
- |
- if (selectorRegExp.regexp.hasMatch(value)) { |
- directives[selectorRegExp.annotation].forEach((type) { |
- directiveRefs.add(new DirectiveRef(node, type, |
- selectorRegExp.annotation, value)); |
- }); |
- } |
- } |
- break; |
- } |
- |
- directiveRefs.sort(_priorityComparator); |
- return directiveRefs; |
- }; |
-} |
- |
-int _directivePriority(NgAnnotation annotation) { |
- if (annotation is NgDirective) { |
- return (annotation.children == NgAnnotation.TRANSCLUDE_CHILDREN) ? 2 : 1; |
- } else if (annotation is NgComponent) { |
- return 0; |
- } |
- throw "Unexpected Type: ${annotation}."; |
-} |
- |
-int _priorityComparator(DirectiveRef a, DirectiveRef b) => |
- _directivePriority(b.annotation) - _directivePriority(a.annotation); |