1- import { Set } from 'facade/lang ' ;
2- // import {AnnotatedType } from './annotated_type ';
1+ import { List , ListWrapper , StringMapWrapper } from 'facade/collection ' ;
2+ import { RegExpWrapper , RegExpMatcherWrapper , CONST , isPresent , isBlank } from 'facade/lang ' ;
33
4- export class Selector {
5- constructor ( directives :Set < AnnotatedType > ) {
6- this . directives = directives ;
4+ const _EMPTY_ATTR_VALUE = '' ;
5+
6+ export class SelectorMatcher {
7+ /* TODO: Add these fields when the transpiler supports fields
8+ _elementMap:Map<String, List>;
9+ _elementPartialMap:Map<String, Selector>;
10+
11+ _classMap:Map<String, List>;
12+ _classPartialMap:Map<String, Selector>;
13+
14+ _attrValueMap:Map<String, Map<String, List>>;
15+ _attrValuePartialMap:Map<String, Map<String, Selector>>;
16+ */
17+ constructor ( ) {
18+ this . _selectables = ListWrapper . create ( ) ;
19+
20+ this . _elementMap = StringMapWrapper . create ( ) ;
21+ this . _elementPartialMap = StringMapWrapper . create ( ) ;
22+
23+ this . _classMap = StringMapWrapper . create ( ) ;
24+ this . _classPartialMap = StringMapWrapper . create ( ) ;
25+
26+ this . _attrValueMap = StringMapWrapper . create ( ) ;
27+ this . _attrValuePartialMap = StringMapWrapper . create ( ) ;
728 }
829
930 /**
10- * When presented with an element description it will return the current set of
11- * directives which are present on the element.
12- *
13- * @param elementName Name of the element
14- * @param attributes Attributes on the Element.
31+ * Add an object that can be found later on by calling `match`.
32+ * @param cssSelector A css selector
33+ * @param selectable An opaque object that will be given to the callback of the `match` function
1534 */
16- visitElement ( elementName :string , attributes :Map < string , string > ) :List < AnnotatedType > {
17- return null ;
35+ addSelectable ( cssSelector :CssSelector , selectable ) {
36+ var matcher = this ;
37+ var element = cssSelector . element ;
38+ var classNames = cssSelector . classNames ;
39+ var attrs = cssSelector . attrs ;
40+
41+ if ( isPresent ( element ) ) {
42+ var isTerminal = attrs . length === 0 && classNames . length === 0 ;
43+ if ( isTerminal ) {
44+ this . _addTerminal ( matcher . _elementMap , element , selectable ) ;
45+ } else {
46+ matcher = this . _addPartial ( matcher . _elementPartialMap , element ) ;
47+ }
48+ }
49+
50+ if ( isPresent ( classNames ) ) {
51+ for ( var index = 0 ; index < classNames . length ; index ++ ) {
52+ var isTerminal = attrs . length === 0 && index === classNames . length - 1 ;
53+ var className = classNames [ index ] ;
54+ if ( isTerminal ) {
55+ this . _addTerminal ( matcher . _classMap , className , selectable ) ;
56+ } else {
57+ matcher = this . _addPartial ( matcher . _classPartialMap , className ) ;
58+ }
59+ }
60+ }
61+
62+ if ( isPresent ( attrs ) ) {
63+ for ( var index = 0 ; index < attrs . length ; index ++ ) {
64+ var isTerminal = index === attrs . length - 1 ;
65+ var attr = attrs [ index ] ;
66+ var attrName = attr . name ;
67+ var attrValue = isPresent ( attr . value ) ? attr . value : _EMPTY_ATTR_VALUE ;
68+ var map = isTerminal ? matcher . _attrValueMap : matcher . _attrValuePartialMap ;
69+ var valuesMap = StringMapWrapper . get ( map , attrName )
70+ if ( isBlank ( valuesMap ) ) {
71+ valuesMap = StringMapWrapper . create ( ) ;
72+ StringMapWrapper . set ( map , attrName , valuesMap ) ;
73+ }
74+ if ( isTerminal ) {
75+ this . _addTerminal ( valuesMap , attrValue , selectable ) ;
76+ } else {
77+ matcher = this . _addPartial ( valuesMap , attrValue ) ;
78+ }
79+ }
80+ }
81+ }
82+ // TODO: map:StringMap when we have a StringMap type...
83+ _addTerminal ( map , name :string , selectable ) {
84+ var terminalList = StringMapWrapper . get ( map , name )
85+ if ( isBlank ( terminalList ) ) {
86+ terminalList = ListWrapper . create ( ) ;
87+ StringMapWrapper . set ( map , name , terminalList ) ;
88+ }
89+ ListWrapper . push ( terminalList , selectable ) ;
90+ }
91+ // TODO: map:StringMap when we have a StringMap type...
92+ _addPartial ( map , name :string ) {
93+ var matcher = StringMapWrapper . get ( map , name )
94+ if ( isBlank ( matcher ) ) {
95+ matcher = new SelectorMatcher ( ) ;
96+ StringMapWrapper . set ( map , name , matcher ) ;
97+ }
98+ return matcher ;
99+ }
100+
101+ /**
102+ * Find the objects that have been added via `addSelectable`
103+ * whose css selector is contained in the given css selector.
104+ * @param cssSelector A css selector
105+ * @param matchedCallback This callback will be called with the object handed into `addSelectable`
106+ */
107+ match ( cssSelector :CssSelector , matchedCallback :Function ) {
108+ var element = cssSelector . element ;
109+ var classNames = cssSelector . classNames ;
110+ var attrs = cssSelector . attrs ;
111+
112+ this . _matchTerminal ( this . _elementMap , element , matchedCallback ) ;
113+ this . _matchPartial ( this . _elementPartialMap , element , cssSelector , matchedCallback ) ;
114+
115+ if ( isPresent ( classNames ) ) {
116+ for ( var index = 0 ; index < classNames . length ; index ++ ) {
117+ var className = classNames [ index ] ;
118+ this . _matchTerminal ( this . _classMap , className , matchedCallback ) ;
119+ this . _matchPartial ( this . _classPartialMap , className , cssSelector , matchedCallback ) ;
120+ }
121+ }
122+
123+ if ( isPresent ( attrs ) ) {
124+ for ( var index = 0 ; index < attrs . length ; index ++ ) {
125+ var attr = attrs [ index ] ;
126+ var attrName = attr . name ;
127+ var attrValue = isPresent ( attr . value ) ? attr . value : _EMPTY_ATTR_VALUE ;
128+
129+ var valuesMap = StringMapWrapper . get ( this . _attrValueMap , attrName )
130+ this . _matchTerminal ( valuesMap , attrValue , matchedCallback ) ;
131+
132+ valuesMap = StringMapWrapper . get ( this . _attrValuePartialMap , attrName )
133+ this . _matchPartial ( valuesMap , attrValue , cssSelector , matchedCallback ) ;
134+ }
135+ }
136+ }
137+ // TODO: map:StringMap when we have a StringMap type...
138+ _matchTerminal ( map , name , matchedCallback ) {
139+ if ( isBlank ( map ) || isBlank ( name ) ) {
140+ return ;
141+ }
142+ var selectables = StringMapWrapper . get ( map , name )
143+ if ( isBlank ( selectables ) ) {
144+ return ;
145+ }
146+ for ( var index = 0 ; index < selectables . length ; index ++ ) {
147+ matchedCallback ( selectables [ index ] ) ;
148+ }
149+ }
150+ // TODO: map:StringMap when we have a StringMap type...
151+ _matchPartial ( map , name , cssSelector , matchedCallback ) {
152+ if ( isBlank ( map ) || isBlank ( name ) ) {
153+ return ;
154+ }
155+ var nestedSelector = StringMapWrapper . get ( map , name )
156+ if ( isBlank ( nestedSelector ) ) {
157+ return ;
158+ }
159+ // TODO(perf): get rid of recursion and measure again
160+ // TODO(perf): don't pass the whole selector into the recursion,
161+ // but only the not processed parts
162+ nestedSelector . match ( cssSelector , matchedCallback ) ;
18163 }
19164}
165+
166+ export class Attr {
167+ @CONST ( )
168+ constructor ( name :string , value :string = null ) {
169+ this . name = name ;
170+ this . value = value ;
171+ }
172+ }
173+
174+ // TODO: Can't use `const` here as
175+ // in Dart this is not transpiled into `final` yet...
176+ var _SELECTOR_REGEXP =
177+ RegExpWrapper . create ( '^([-\\w]+)|' + // "tag"
178+ '(?:\\.([-\\w]+))|' + // ".class"
179+ '(?:\\[([-\\w*]+)(?:=([^\\]]*))?\\])' ) ; // "[name]", "[name=value]" or "[name*=value]"
180+
181+ export class CssSelector {
182+ static parse ( selector :string ) :CssSelector {
183+ var element = null ;
184+ var classNames = ListWrapper . create ( ) ;
185+ var attrs = ListWrapper . create ( ) ;
186+ selector = selector . toLowerCase ( ) ;
187+ var matcher = RegExpWrapper . matcher ( _SELECTOR_REGEXP , selector ) ;
188+ var match ;
189+ while ( isPresent ( match = RegExpMatcherWrapper . next ( matcher ) ) ) {
190+ if ( isPresent ( match [ 1 ] ) ) {
191+ element = match [ 1 ] ;
192+ }
193+ if ( isPresent ( match [ 2 ] ) ) {
194+ ListWrapper . push ( classNames , match [ 2 ] ) ;
195+ }
196+ if ( isPresent ( match [ 3 ] ) ) {
197+ ListWrapper . push ( attrs , new Attr ( match [ 3 ] , match [ 4 ] ) ) ;
198+ }
199+ }
200+ return new CssSelector ( element , classNames , attrs ) ;
201+ }
202+ // TODO: do a toLowerCase() for all arguments
203+ @CONST ( )
204+ constructor ( element :string , classNames :List < string > , attrs :List < Attr > ) {
205+ this . element = element ;
206+ this . classNames = classNames ;
207+ this . attrs = attrs ;
208+ }
209+
210+ toString ( ) :string {
211+ var res = '' ;
212+ if ( isPresent ( this . element ) ) {
213+ res += this . element ;
214+ }
215+ if ( isPresent ( this . classNames ) ) {
216+ for ( var i = 0 ; i < this . classNames . length ; i ++ ) {
217+ res += '.' + this . classNames [ i ] ;
218+ }
219+ }
220+ if ( isPresent ( this . attrs ) ) {
221+ for ( var i = 0 ; i < this . attrs . length ; i ++ ) {
222+ var attr = this . attrs [ i ] ;
223+ res += '[' + attr . name ;
224+ if ( isPresent ( attr . value ) ) {
225+ res += '=' + attr . value ;
226+ }
227+ res += ']' ;
228+ }
229+ }
230+ return res ;
231+ }
232+ }
0 commit comments