| 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);
|
|
|