OLD | NEW |
| (Empty) |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 library template_binding.src.mustache_tokens; | |
6 | |
7 import 'package:observe/observe.dart'; | |
8 | |
9 // Dart note: this was added to decouple the parse function below from the rest | |
10 // of template_binding. This allows using this code in command-line tools as | |
11 // well. | |
12 typedef Function DelegateFunctionFactory(String pathString); | |
13 | |
14 /** | |
15 * Represents a set of parsed tokens from a {{ mustache binding expression }}. | |
16 * This can be created by calling [parse]. | |
17 * | |
18 * For performance reasons the data is stored in one linear array in [_tokens]. | |
19 * This class wraps that array and provides accessors in an attempt to make the | |
20 * pattern easier to understand. See [length] and [getText] for example. | |
21 */ | |
22 class MustacheTokens { | |
23 // Constants for indexing into the exploded structs in [_tokens] . | |
24 static const _TOKEN_TEXT = 0; | |
25 static const _TOKEN_ONETIME = 1; | |
26 static const _TOKEN_PATH = 2; | |
27 static const _TOKEN_PREPAREFN = 3; | |
28 static const _TOKEN_SIZE = 4; | |
29 | |
30 // There is 1 extra entry for the end text. | |
31 static const _TOKEN_ENDTEXT = 1; | |
32 | |
33 bool get hasOnePath => _tokens.length == _TOKEN_SIZE + _TOKEN_ENDTEXT; | |
34 bool get isSimplePath => hasOnePath && | |
35 _tokens[_TOKEN_TEXT] == '' && _tokens[_TOKEN_SIZE + _TOKEN_TEXT] == ''; | |
36 | |
37 /** | |
38 * [TEXT, (ONE_TIME?, PATH, DELEGATE_FN, TEXT)+] if there is at least one | |
39 * mustache. | |
40 */ | |
41 final List _tokens; | |
42 | |
43 final bool onlyOneTime; | |
44 | |
45 // Dart note: I think this is cached in JavaScript to avoid an extra | |
46 // allocation per template instance. Seems reasonable, so we do the same. | |
47 Function _combinator; | |
48 Function get combinator => _combinator; | |
49 | |
50 MustacheTokens._(this._tokens, this.onlyOneTime) { | |
51 // Should be: [TEXT, (ONE_TIME?, PATH, DELEGATE_FN, TEXT)+]. | |
52 assert((_tokens.length - _TOKEN_ENDTEXT) % _TOKEN_SIZE == 0); | |
53 | |
54 _combinator = hasOnePath ? _singleCombinator : _listCombinator; | |
55 } | |
56 | |
57 int get length => _tokens.length ~/ _TOKEN_SIZE; | |
58 | |
59 /** | |
60 * Gets the [i]th text entry. Note that [length] can be passed to get the | |
61 * final text entry. | |
62 */ | |
63 String getText(int i) => _tokens[i * _TOKEN_SIZE + _TOKEN_TEXT]; | |
64 | |
65 /** Gets the oneTime flag for the [i]th token. */ | |
66 bool getOneTime(int i) => _tokens[i * _TOKEN_SIZE + _TOKEN_ONETIME]; | |
67 | |
68 /** Gets the path for the [i]th token. */ | |
69 PropertyPath getPath(int i) => _tokens[i * _TOKEN_SIZE + _TOKEN_PATH]; | |
70 | |
71 /** Gets the prepareBinding function for the [i]th token. */ | |
72 Function getPrepareBinding(int i) => | |
73 _tokens[i * _TOKEN_SIZE + _TOKEN_PREPAREFN]; | |
74 | |
75 | |
76 /** | |
77 * Parses {{ mustache }} bindings. | |
78 * | |
79 * Returns null if there are no matches. Otherwise returns the parsed tokens. | |
80 */ | |
81 static MustacheTokens parse(String s, [DelegateFunctionFactory fnFactory]) { | |
82 if (s == null || s.isEmpty) return null; | |
83 | |
84 var tokens = null; | |
85 var length = s.length; | |
86 var lastIndex = 0; | |
87 var onlyOneTime = true; | |
88 while (lastIndex < length) { | |
89 var startIndex = s.indexOf('{{', lastIndex); | |
90 var oneTimeStart = s.indexOf('[[', lastIndex); | |
91 var oneTime = false; | |
92 var terminator = '}}'; | |
93 | |
94 if (oneTimeStart >= 0 && | |
95 (startIndex < 0 || oneTimeStart < startIndex)) { | |
96 startIndex = oneTimeStart; | |
97 oneTime = true; | |
98 terminator = ']]'; | |
99 } | |
100 | |
101 var endIndex = -1; | |
102 if (startIndex >= 0) { | |
103 endIndex = s.indexOf(terminator, startIndex + 2); | |
104 } | |
105 | |
106 if (endIndex < 0) { | |
107 if (tokens == null) return null; | |
108 | |
109 tokens.add(s.substring(lastIndex)); // TEXT | |
110 break; | |
111 } | |
112 | |
113 if (tokens == null) tokens = []; | |
114 tokens.add(s.substring(lastIndex, startIndex)); // TEXT | |
115 var pathString = s.substring(startIndex + 2, endIndex).trim(); | |
116 tokens.add(oneTime); // ONETIME? | |
117 onlyOneTime = onlyOneTime && oneTime; | |
118 var delegateFn = fnFactory == null ? null : fnFactory(pathString); | |
119 // Don't try to parse the expression if there's a prepareBinding function | |
120 if (delegateFn == null) { | |
121 tokens.add(new PropertyPath(pathString)); // PATH | |
122 } else { | |
123 tokens.add(null); | |
124 } | |
125 tokens.add(delegateFn); // DELEGATE_FN | |
126 | |
127 lastIndex = endIndex + 2; | |
128 } | |
129 | |
130 if (lastIndex == length) tokens.add(''); // TEXT | |
131 | |
132 return new MustacheTokens._(tokens, onlyOneTime); | |
133 } | |
134 | |
135 | |
136 // Dart note: split "combinator" into the single/list variants, so the | |
137 // argument can be typed. | |
138 String _singleCombinator(Object value) { | |
139 if (value == null) value = ''; | |
140 return '${getText(0)}$value${getText(length)}'; | |
141 } | |
142 | |
143 String _listCombinator(List<Object> values) { | |
144 var newValue = new StringBuffer(getText(0)); | |
145 int len = this.length; | |
146 for (var i = 0; i < len; i++) { | |
147 var value = values[i]; | |
148 if (value != null) newValue.write(value); | |
149 newValue.write(getText(i + 1)); | |
150 } | |
151 return newValue.toString(); | |
152 } | |
153 } | |
OLD | NEW |